Java基礎-多線程-①線程的創建和啟動


簡單闡釋進程和線程

對於進程最直觀的感受應該就是“windows任務管理器”中的進程管理:

  (計算機原理課上的記憶已經快要模糊了,簡單理解一下):一個進程就是一個“執行中的程序”,是程序在計算機上的一次運行活動。程序要運行,系統就在內存中為該程序分配一塊獨立的內存空間,載入程序代碼和資源進行執行。程序運行期間該內存空間不能被其他進程直接訪問。系統以進程為基本單位進行系統資源的調度和分配。何為線程?線程是進程內一次具體的執行任務。程序的執行具體是通過線程來完成的,所以一個進程中至少有一個線程。回憶一下 HelloWrold 程序中main方法的執行,其實這時候,Java虛擬機會開啟一個名為“main”的線程來執行程序代碼。一個進程可以包含多個線程,這些線程共享數據空間和資源,但又分別擁有各自的執行堆棧和程序計數器。線程是CPU調度的基本單位。

多線程

  一個進程包含了多個線程,自然就叫做多線程。擁有多個線程就可以讓程序看起來可以“同時”處理多個任務,為什么是看起來呢?因為CPU也分身乏術,只能讓你這個線程執行一會兒,好了你歇着,再讓另一個線程執行一會兒,下次輪到你的時候你再繼續執行。這里的“一會兒”實際上時間非常短,感覺上就是多個任務“同時”在執行。CPU就這樣不停的切來切去…既然CPU一次也只能執行一個線程,為什么要使用多線程呢?當然是為了充分利用CPU資源。一個線程執行過程中不可能每時每刻都在占用CPU,CPU歇着的時候我們就可以讓它切過來執行其他的線程。

  比如QQ聊天的時候,跟一個人正聊着呢,另一個消息過來了。如果是單線程,不好意思,等我跟這一個聊完說拜拜之后再去理你吧。多線程呢,消息窗口全打開,這個窗口說完話了,總得等人家回吧,趁這個空閑時候,處理另一個窗口的消息。這樣看起來不就是同時進行了么,每一個窗口的另一邊都以為你只在跟他一個人聊天…

Java中的多線程

  Java中啟用多線程有兩種方式:①繼承Thread類;②實現Runnable接口

There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread. An instance of the subclass can then be allocated and started.The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creating Thread, and started. 

繼承Thread類

  創建一個類,繼承java.lang.Thread,並覆寫Thread類的run()方法,該類的實例就可以作為一個線程對象被開啟。

/**
 * Dog類,繼承了Thread類
 * @author lt
 */
class Dog extends Thread {
    /*
     * 覆寫run()方法,定義該線程需要執行的代碼
     */
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
        }
    }
}

線程創建好了,怎么讓它作為程序的一個獨立的線程被執行呢?創建一個該類的實例,並調用start()方法,將開啟一個線程,並執行線程類中覆寫的run()方法。

public class ThreadDemo {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.start();
    }
}

看不出什么端倪,如果我們直接調用實例的run()方法,執行效果是完全一樣的,見上圖。

public class ThreadDemo {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.run();
    }
}

如果一切正常,這時候程序中應該有兩個線程:一個主線程main,一個新開啟的線程。run()方法中的代碼究竟是哪個線程執行的呢?Java程序中,一個線程開啟會被分配一個線程名:Thread-x,x從0開始。我們可以打印當前線程的線程名,來看看究竟是誰在執行代碼。

class Dog extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("當前線程:" + Thread.currentThread().getName() + "---" + i);
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        System.out.println("當前線程:" + Thread.currentThread().getName());
        Dog dog = new Dog();
        dog.start();
    }
}

可以看到,確實開啟了一個新的線程:Thread-0,main()方法的線程名就叫main。

同一個實例只能調用一次start()方法開啟一次,多次開啟,將報java.lang.IllegalThreadStateException異常:

我們再創建一個實例,開啟第三個線程:

public class ThreadDemo {
    public static void main(String[] args) {
        System.out.println("當前線程:" + Thread.currentThread().getName());
        Dog dog = new Dog();
        Dog dog2 = new Dog();
        dog.start();
        dog2.start();
    }
}

這時候我們已經能夠看到多線程的底層實現原理:CPU切換處理、交替執行的效果了。

run和start

  上面我們直接調用run()方法和調用start()方法的結果一樣,現在我們在打印線程名的情況下再來看看:

public class ThreadDemo {
    public static void main(String[] args) {
        System.out.println("當前線程:" + Thread.currentThread().getName());
        Dog dog = new Dog();
        Dog dog2 = new Dog();
        dog.run();
        dog2.run();
    }
}

可以看到,這時候並沒有開啟新的線程,main線程直接調用執行了run()方法中的代碼。所以start()方法會開啟新的線程並在新的線程中執行run()方法中的代碼,而run()方法不會開啟線程。查看start()的源代碼,該方法調用了本地方法 private native void start0();即調用的是操作系統的底層函數:

public synchronized void start() {
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

private native void start0();

實現Runnable接口

  第二種方式,實現Runnable接口,並覆寫接口中的run()方法,這是推薦的也是最常用的方式。Runnable接口定義非常簡單,就只有一個抽象的run()方法。

//Runnable接口源碼
public interface Runnable {
    public abstract void run();
}
class Dog implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("當前線程:" + Thread.currentThread().getName() + "---" + i);
        }
    }
}

這時候的Dog類看起來跟線程什么的毫無關系,也沒有了start()方法,怎么樣開啟一個新的線程呢?直接調用run()方法?想想也不行。這時候我們需要將一個Dog類的實例,作為Thread類的構造函數的參數傳入,來創建一個Thread類的實例,並通過該Thread類的實例來調用start()方法從而開啟線程。

public class ThreadDemo {
    public static void main(String[] args) {
        System.out.println("當前線程:" + Thread.currentThread().getName());
        Dog dog = new Dog();
        Thread thread = new Thread(dog);
        thread.start();
    }
}

這時候如果要開啟第三個線程,需要創建一個新的Thread類的實例,同時傳入剛才的Dog類的實例(當然也可以創建一個新的Dog實例)。這時候我們就可以看到跟繼承Thread類的方式的區別:多個線程可以共享同一個Dog類的實例。

public class ThreadDemo {
    public static void main(String[] args) {
        System.out.println("當前線程:" + Thread.currentThread().getName());
        Dog dog = new Dog();
        Thread thread = new Thread(dog);
        Thread thread2 = new Thread(dog);
        thread.start();
        thread2.start();
    }
}

兩種方式的比較

  繼承Thread類的方式有它固有的弊端,因為Java中繼承的單一性,繼承了Thread類就不能繼承其他類了;同時也不符合繼承的語義,Dog跟Thread沒有直接的父子關系,繼承Thread只是為了能擁有一些功能特性。而實現Runnable接口,避免了單一繼承的局限性,同時更符合面向對象的編程方式,即將線程對象進行單獨的封裝,而且實現接口的方式降低了線程對象(Dog)和線程任務(run方法中的代碼)的耦合性,如上面所述,可以使用同一個Dog類的實例來創建並開啟多個線程,非常方便的實現資源的共享。實際上Thread類也是實現了Runnable接口。實際開發中多是使用實現Runnable接口的方式。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM