1. 程式人生 > >一、Java多線程基礎

一、Java多線程基礎

threading oob ldd stack idt add ron ref 隨著

一、簡介

1、操作系統

在早起的裸機時代,計算機非常地昂貴,而且也沒有操作系統的概念,計算機從頭到尾只能執行一個程序。如果程序在執行一個耗時的操作,那麽在這個過程中,計算機就有大量的資源閑置在那裏,這是非常浪費的。

而這個時候,操作系統的概念被提出了。在操作系統的控制下,一個計算機可以執行很多的程序。計算機的資源由操作系統進行分配,程序之間獲得計算機資源並執行各自的任務,相互獨立。操作系統的出現使得計算機資源的利用率大大增加。

你也可以將操作系統理解為,運行程序的程序。類比AI是一種產生算法的算法。

2、進程和線程

操作系統可以執行多個程序,每個程序在計算機中即是一個“進程”。進程的執行是並行的,實際上這裏的“並行”並不完全是物理意義上的並行。操作系統會給每個進程分配一定的執行時間,並且CPU在這些進程之間快速地切換執行,從而近似地達到了一種並行執行的效果。當然,現代CPU也從早期的單核發展為4核、8核,可以從物理上同時執行。即使是單核,也能夠控制多個執行流達到物理上並發的目的。

操作系統使得程序得以並發執行,但隨著需求的增加,我們希望每個程序也能夠並發多個任務。所以,線程的概念就應運而生了。線程是依賴於進程的,共享進程的計算機資源。一個進程可以擁有一到多個線程。線程的並行執行和進程的理念是一樣的,共享進程的時間片,快速地執行切換。

3、Java多線程

Java編程語言為幫助開發者開發多線程的應用程序設計了一套簡單的語義,你可以利用Java的內置支持來構建多線程程序,充分利用CPU資源,提高程序的響應速度

二、Java線程的生命周期

下圖,摘錄自菜鳥教程:http://www.runoob.com/java/java-multithreading.html

技術分享圖片

這個圖相信大家都很熟悉,它是表達了線程的生命周期,包含了幾個狀態,如下:

1)創建:當我們在程序中new出來一個線程實例對象的時候,線程便處於創建狀態;

2)就緒:當我們調用了start()方法,表示該線程即將等待JVM的native方法去調用我們的線程,也就是就緒狀態;

3)運行:如果JVM調用了我們的線程,線程實例執行中,它就處於運行狀態;

4)阻塞:運行過程中可能出現sleep、wait或者其它IO操作等,這個時候當前的線程就處於阻塞狀態,讓出CPU的資源,而不是繼續占用。

5)死亡:如果線程正常執行完畢,手動退出或者意外結束,那麽線程就進入了死亡狀態,該線程占用的資源也會被自動回收。

三、使用示例

Java顯示創建線程有兩種實現:

1)繼承Thread類

public class ThreadDemo extends Thread {

    @Override
    public void run() {
        System.out.println("執行了" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        new ThreadDemo().start();
        System.out.println("主線程執行了" + Thread.currentThread().getName());
    }
    
}

事實上,Thread類實現了Runnable接口,而我們重寫了Runnable的run方法,當調用start()方法的時候,該線程會調用native方法,從而JVM會去異步執行線程實例的run方法。

2)實現了Runnable接口

public class RunnableDemo implements Runnable {
    @Override
    public void run() {
        System.out.println("線程執行" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        new Thread(new RunnableDemo()).start();
        System.out.println("主線程執行" + Thread.currentThread().getName());
    }
}

實現Runnable接口的方式,也需要將Runnable作為Thread實例的構造入參,然後通過start()將線程轉換為就緒狀態。如果你直接調用run方法是不會異步運行的。

以上兩種實現,其實都是基於Runnable接口和Thread類,只是寫法不同。

四、常用API

  1. currentThread():靜態方法,返回當前線程的引用
  2. getId():返回當前線程的標識符
  3. getName():返回當前線程的引用
  4. getPriority():返回當前線程的優先級
  5. getState():返回當前線程的狀態
  6. interrupt():中斷線程(僅是打上中斷標識,無法強制停止線程)
  7. interrupted():靜態方法,返回是否中斷
  8. isAlive():是否存活
  9. isDaemon():是否守護線程
  10. isInterrupted():是否中斷
  11. yield():靜態方法,暫停當前線程,執行其它線程
  12. start():使線程進入就緒狀態
  13. sleep(long millis):靜態方法,使線程休眠
  14. setPriority(int priority):設置優先級,優先級高的會先執行
  15. setName():設置名字
  16. setDaemon():設置為守護線程
  17. join():等待其它線程中止以後再繼續執行
  18. wait():等待notify喚醒
  19. notify():喚醒wait線程

yield示例

yield執行的時候暫停當前線程執行,讓出CPU資源,去執行其它線程

public class YieldDemo {

    private static volatile boolean isReady = false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "讓主線程執行");
            while (!isReady) {
                Thread.yield();
            }
            System.out.println(Thread.currentThread().getName() + "執行完畢");
        }).start();
        Thread.sleep(5000);
        isReady = true;
        System.out.println(Thread.currentThread().getName() + "執行完畢");
    }
}

join示例

join的作用是讓將某個線程插入到當前線程的執行位置,並等待該線程執行完畢以後再執行當前線程。

public class JoinDemo {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "開始執行");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "執行完畢");
        });
        thread.start();
        System.out.println(Thread.currentThread().getName() + "等待子線程執行完畢");
        thread.join();
        System.out.println(Thread.currentThread().getName() + "執行完畢");
    }
}

notify/wait示例

notify和wait使用比較特殊,需要獲得同步鎖資源

notify和wait方法是Object對象的方法,任何對象都會有。它的作用是什麽呢?我們舉一個場景示例:

1)存在兩個線程A和B;

2)我們希望A執行完畢以後通知B去執行,而B在A通知之前一直等待

代碼示例如下:

public class WaitDemo {

    private static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                // 要先獲得同步鎖
                synchronized (lock) {
                    try {
                        System.out.println(Thread.currentThread().getName() + " waiting");
                        // 等待notify
                        lock.wait();
                        System.out.println(Thread.currentThread().getName() + " end waiting");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        System.out.println("sleep");
        Thread.sleep(5000);
        // 要先獲得同步鎖
        synchronized (lock) {
            System.out.println("notify");
            // 執行notify
            lock.notifyAll();
            System.out.println("finished");
        }
    }
}

這裏隨意建立了一個lock對象作為鎖對象,子線程拿到這個鎖以後執行等待。而主線程拿到鎖以後,發送通知,執行結果如下:

sleep
Thread-0 waiting
Thread-1 waiting
Thread-2 waiting
notify
finished
Thread-2 end waiting
Thread-1 end waiting
Thread-0 end waiting

註意:如果notifyAll()在wait()之前執行了,那麽子線程將無線等待下去,你可以給wait設置超時時間

更多API示例,參考JDK文檔:http://tool.oschina.net/uploads/apidocs/jdk-zh/java/lang/Thread.html

一、Java多線程基礎