Java基礎-多線程-①線程的建立和啓動

簡單闡釋進程和線程java

對於進程最直觀的感覺應該就是「windows任務管理器」中的進程管理:編程

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

多線程多線程

  一個進程包含了多個線程,天然就叫作多線程。擁有多個線程就可讓程序看起來能夠「同時」處理多個任務,爲何是看起來呢?由於CPU也分身乏術,只能讓你這個線程執行一下子,好了你歇着,再讓另外一個線程執行一下子,下次輪到你的時候你再繼續執行。這裏的「一下子」實際上時間很是短,感受上就是多個任務「同時」在執行。CPU就這樣不停的切來切去…既然CPU一次也只能執行一個線程,爲何要使用多線程呢?固然是爲了充分利用CPU資源。一個線程執行過程當中不可能每時每刻都在佔用CPU,CPU歇着的時候咱們就可讓它切過來執行其餘的線程。ide

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

Java中的多線程post

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

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. spa

繼承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接口的方式。

相關文章
相關標籤/搜索