多線程相關

多線程相關

1. 進程與線程

進程是程序的一次執行過程,是系統運行程序的基本單位,所以進程是動態的。系統運行一個程序便是一個進程從建立、運行到消亡的過程。java

線程是比進程更小的執行單位,一個進程在其執行的過程當中能夠產生多個線程。線程共享進程的堆和方法區的資源,同時線程還有私有的程序計數器、虛擬機棧和本地方法棧資源。安全

2. 並行與併發

並行:單位時間內,多個任務同時執行。多線程

併發:同一時間段,多個任務都在執行(單位時間內不必定同時執行)。併發

3. 線程的生命週期(狀態)

image-20200816214940281

狀態名稱 說明
NEW 初始狀態,線程被構建
RUNNABLE 運行狀態,包括運行中和就緒兩種狀態
BLOCKED 阻塞狀態,表示線程阻塞於鎖
WAITING 等待狀態,表示線程進入等待狀態,若是其餘線程不通知則不會喚醒
TIMED_WAITING 超時等待狀態,通過指定等待時間後會自動喚醒
TERMINATED 終止狀態,表示線程已經執行完畢

4. 線程的建立方式

4.1 繼承Thread類

經過繼承Thread類並重寫run() 方法能夠建立線程,調用start()方法來啓動線程。ide

public class ThreadDemo01 {
    public static void main(String[] args) {
        MyThread01 t = new MyThread01();
        t.start(); // 線程名稱:Thread-0
    }
}

/**
 * 繼承Thread類
 */
class MyThread01 extends Thread {
    @Override
    public void run() {
        System.out.println("線程名稱:" + Thread.currentThread().getName());
    }
}

因爲Java中類的單繼承特性,當一個類繼承Thread類後就不能繼承其它的類了。線程

4.2 實現Runnable接口

經過實現Runnable接口並重寫run() 方法能夠建立一個線程,同時能夠繼承其它的類。code

public class ThreadDemo02 {
    public static void main(String[] args) {
        new Thread(new MyThread02()).start(); // 線程名稱:Thread-0
    }
}

/**
 * 實現Runnable接口
 */
class MyThread02 extends Object implements Runnable {
    @Override
    public void run() {
        System.out.println("線程名稱:" + Thread.currentThread().getName());
    }
}

採用這種方式建立線程時能夠利用JDK1.8的新特性lambda表達式,無需實現Runnable接口的實現類,簡化代碼。blog

public class ThreadDemo02 {
    public static void main(String[] args) {
        new Thread(() -> System.out.println(Thread.currentThread().getName())).start();
    }
}

4.3 實現Callable接口

有返回值的任務必須實現Callable接口並從新call() 方法,返回值封裝在future中,經過get()方法獲取返回的Object,再結合線程池接口ExecutorService實現有返回值得線程運行。繼承

import java.util.concurrent.*;

public class ThreadDemo03 {
    public static void main(String[] args) {
        // 建立單個線程的線程池
        ExecutorService es = Executors.newSingleThreadExecutor();
        // 提交任務到線程池並獲取執行結果
        Future<Integer> future = es.submit(new MyThread03());
        // 關閉線程池
        es.shutdown();
        try {
            if (future.get() != null) {
                System.out.println("Callable子線程計算結果:" + future.get());
            } else {
                System.out.println("Callable子線程未獲取到結果");
            }
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 實現Callable接口
 */
class MyThread03 implements Callable<Integer> {
    private int sum;
    @Override
    public Integer call() throws Exception {
        System.out.println("Callable子線程開始計算...");
        Thread.sleep(1000);
        for (int i = 0; i < 10; i++) {
            sum += i;
        }
        System.out.println("Callable子線程計算結束...");
        return sum;
    }
}

運行結果:接口

image-20200816232533737

5. 線程終止的方式

  • 正常運行結束:程序運行結束,線程自動結束;

  • 使用退出標誌退出線程:設置一個boolean類型的標誌,經過設置標誌的值終止線程;

    public class ThreadTerminatedDemo01 extends Thread{
        public volatile boolean exit = false;
    
        @Override
        public void run() {
            while (!exit) {
                System.out.println(Thread.currentThread().getName());
            }
        }
    }
  • interrupt() 方法中斷線程

    • 線程出與阻塞狀態:調用interrupt()方法時會拋出InterruptedException異常。阻塞中哪一個方法拋出這個異常,經過代碼捕獲該異常,而後break跳出循環狀態,使得有機會結束這個線程的執行。
    • 線程處於爲阻塞狀態:使用isInterrupt() 判斷線程的中斷標誌位退出循環。當使用interrupt() 方法時,中斷標誌就會設置爲true,和使用自定義的標誌控制循環是相同的原理。
    public class ThreadTerminatedDemo02 extends Thread{
        @Override
        public void run() {
            // 非阻塞狀態下經過判斷中斷標誌來退出
            while (!isInterrupted()) {
                try {
                    // 阻塞狀態下捕獲中斷異常來退出
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    // 捕獲到異常後執行break跳出循環
                    break;
                }
            }
        }
    }
  • stop() 方法終止線程(線程不安全)

    程序中直接使用Thread.stop()方法能夠強行終止線程,但會有線程不安全問題。當調用Thread.stop()方法後,線程拋出ThreadDeathError異常,而且會釋放其持有的全部鎖,從而可能致使數據出現不一致的狀況。

6. 死鎖與死鎖避免

6.1 死鎖

線程之間因爲相互競爭資源或調度不當而同時被阻塞,它們中的一個或所有都在等待某個資源被釋放。

public class ThreadDeadLockDemo {
    private static Object r1 = new Object();
    private static Object r2 = new Object();

    public static void main(String[] args) {
        // Thread01
        new Thread(() -> {
            synchronized (r1) {
                try {
                    System.out.println(Thread.currentThread().getName() + " has got r1.");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " is waiting for r2.");
                synchronized (r2) {
                    System.out.println(Thread.currentThread().getName() + " has got r2.");
                }
            }
        }, "Thread01").start();

        // Thread02
        new Thread(() -> {
            synchronized (r2) {
                System.out.println(Thread.currentThread().getName() + " has got r2.");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " is waiting for r1.");
                synchronized (r1) {
                    System.out.println(Thread.currentThread().getName() + " has got r1.");
                }
            }
        }, "Thread02").start();
    }
}

執行結果:

image-20200816235408033

6.2 死鎖產生的四個必要條件

互斥:該資源任意一個時刻智能由一個線程佔用;

請求保持:一個進程因請求資源而阻塞時不會釋放已經得到的資源;

不可剝奪:一個線程已經得到的資源不能被其它線程強行剝奪,只有等使用結束纔會被釋放;

循環等待:若干進程之間造成一種頭尾相接的循環等待資源關係。

6.3 死鎖避免

破壞產生死鎖的四個必要條件中的一個。

破壞互斥條件:沒法破壞。

破壞請求保持條件:線程一次性申請全部的資源。

破壞不可剝奪條件:佔用部分資源的線程進一步申請資源時,若是申請不到能夠主動釋放已佔有的資源。

破壞循環等待條件:靠按序申請預防。按某一順序申請資源,釋放資源則反序釋放,破壞循環等待條件。

public class BreakDeadLockDemo {
    private static Object r1 = new Object();
    private static Object r2 = new Object();

    public static void main(String[] args) {
        // Thread01
        new Thread(() -> {
            synchronized (r1) {
                try {
                    System.out.println(Thread.currentThread().getName() + " has got r1.");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " is waiting for r2.");
                synchronized (r2) {
                    System.out.println(Thread.currentThread().getName() + " has got r2.");
                }
            }
        }, "Thread01").start();

        // Thread02
        new Thread(() -> {
            synchronized (r1) {
                System.out.println(Thread.currentThread().getName() + " has got r1.");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " is waiting for r1.");
                synchronized (r2) {
                    System.out.println(Thread.currentThread().getName() + " has got r2.");
                }
            }
        }, "Thread02").start();
    }
}

對Thread02線程進行修改後,破壞循環等待條件,從而避免死鎖。執行結果以下:

image-20200817000049472

7. 比較sleep()和wait()方法

  • 最大區別:sleep()不會釋放鎖,wait()方法會釋放鎖;
  • 二者均可以暫停線程的執行;
  • sleep()一般被用於暫停執行,wait()一般用於線程間通訊/交互;
  • wait()方法調用後,線程不會自動喚醒,須要其餘線程調用notify()/notifyAll()方法。sleep(long timeout)方法或者wait(long timeout)能夠指定線程休眠的時間,超時後線程會自動喚醒。

8. 比較run()和start()方法

建立一個線程時須要重寫run()方法,調用start()方法後會啓動該線程。調用start()方法會執行線程的相應準備工做而後自動執行run()方法的內容,這是真正的多線程工做。執行run()方法會把run()方法當成main線程下的普通方法去執行,並不會再某個線程中執行,不是多線程的工做。

調用start()方法能夠啓動線程並使該線程進入就緒狀態,而執行run()方法只是線程中的一個普通方法調用,仍在main線程裏執行。

相關文章
相關標籤/搜索