Java JUC&多執行緒 基礎完整版

MineLSG發表於2024-05-24

Java JUC&多執行緒 基礎完整版

目錄
  • Java JUC&多執行緒 基礎完整版
      • 1、 多執行緒的第一種啟動方式之繼承Thread類
      • 2、多執行緒的第二種啟動方式之實現Runnable介面
      • 3、多執行緒的第三種實現方式之實現Callable介面
      • 4、多線的常用成員方法
      • 5、執行緒的優先順序
      • 6、守護執行緒
      • 7、執行緒的讓出
      • 8、執行緒插隊
      • 9、同步程式碼塊
      • 10、同步方法
      • 11、執行緒鎖
      • 12、死鎖問題
      • 13、等待喚醒機制(消費者模式)
      • 14、阻塞佇列下的等待喚醒機制
  • 執行緒池
      • 1、執行緒池的建立
      • 2、自定義執行緒池

1、 多執行緒的第一種啟動方式之繼承Thread類

優點: 比較簡單,可以直接使用Thread類中的方法,缺點: 可以擴充性比較差,不能再繼承其他的類

執行緒類MyThread

public class MyThread extends Thread {


    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "Hello World");
        }
    }
}

執行類ThreadDemo

public class ThreadDemo {

    public static void main(String[] args) {
        /*
         * 多執行緒第一種啟動方式
         * 1. 自己定義一個類繼承Thread
         * 2. 重寫run方法
         * 3. 建立啟動物件,並啟動執行緒
         */

        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.setName("執行緒1");
        t2.setName("執行緒2");
        t1.start();
        t2.start();
    }

}

2、多執行緒的第二種啟動方式之實現Runnable介面

優點: 擴充性強,實現該介面的同時可以繼承其他的類,缺點: 相對複雜,不能直接使用Thread類中的方法

執行緒類MyRunnable

public class MyRunnable implements Runnable {

    @Override
    public void run() {
        // 執行緒要執行的程式碼
        for (int i = 0; i < 100; i++) {
            // 先獲取當前執行緒的物件
            Thread thread = Thread.currentThread();
            System.out.println(thread.getName() + "Hello World");
        }
    }
}

執行類ThreadDemo

public class ThreadDemo {

    public static void main(String[] args) {
        /*
         * 多執行緒的第二種啟動方式
         * 1. 自定義一個類實現Runnable介面
         * 2. 重寫裡面的run方法
         * 3. 建立自己的類物件
         * 4.建立一個Thread類的物件,並開啟執行緒
         *
         */

        // 任務物件
        MyRunnable myRun = new MyRunnable();
        // 執行緒物件
        Thread t1 = new Thread(myRun);
        Thread t2 = new Thread(myRun);

        // 給執行緒設定名字
        t1.setName("執行緒1");
        t2.setName("執行緒2");
        // 開啟執行緒
        t1.start();
        t2.start();
    }
}

3、多執行緒的第三種實現方式之實現Callable介面

優點: 擴充性強,實現該介面的同時可以繼承其他的類,相對複雜,不能直接使用Thread類中的方法

執行緒類MyCallable

public class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        // 求1~100之間的和
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            sum = sum + i;
        }
        return sum;
    }
}

執行類ThreadDemo

public class ThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        /*
         * 多執行緒的第三種實現方式
         *   特點: 可以獲取到多執行緒的執行結果
         *  1. 建立一個MyCallable類實現Callable介面
         *  2. 重寫call (是有返回值的,表示多執行緒執行的結果)
         *  3. 建立MyCallable的物件 (表示多執行緒要執行的任務)
         *  4. 建立FutureTask的物件 (管理多執行緒執行的結果)
         *  5. 建立Thread類的物件 並啟動(表示執行緒)
         */

        // 建立MyCallable物件 (表示要執行的多執行緒的任務)
        MyCallable mc = new MyCallable();
        // 建立FutureTask的物件 (作用管理多執行緒執行的結果)
        FutureTask<Integer> ft = new FutureTask<>(mc);
        // 建立執行緒物件
        Thread t1 = new Thread(ft);
        // 啟動執行緒
        t1.start();

        // 獲取多執行緒執行的結果
        Integer result = ft.get();
        System.out.println(result);

    }
}

4、多線的常用成員方法

  • String getName() 返回此執行緒的名稱
  • void setName(String name) 設定執行緒名稱(構造方法也可以設定名稱)
    • 細節
      • 1.如果不設定執行緒名稱,執行緒也是有預設序號的,從0開始格式為Thread-X
      • 2.如果給執行緒設定名稱可以使用setName和子類的構造方法
    • 細節
      • 當jvm虛擬機器啟動後會自動啟動多條執行緒,其中就有一個執行緒名字為main他的作用就是呼叫main方法,執行裡面的程式碼
  • static void sleep(long time) 讓執行緒休眠指定的時間, 單位為毫秒
    • 細節
      • 哪條執行緒執行到了這個方法,那麼哪條執行緒就會在這裡停留相對於的時間方法的引數就表示睡眠的時間,單位為毫秒時間到了後執行緒會自動甦醒,並繼續執行

執行緒類MyThread

public class MyThread extends Thread {

    // 構造方式設定執行緒名稱
    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "@" + i);
        }
    }
}

執行類ThreadDemo

public class ThreadDemo {

    public static void main(String[] args) {

        // 1.建立執行緒的物件
        MyThread t1 = new MyThread("飛機");
        MyThread t2 = new MyThread("坦克");


        // 2.開啟執行緒
        t1.start();
        t2.start();

        // 哪條執行緒執行到這個方法,此時獲取的就是哪條執行緒的物件
        Thread thread = Thread.currentThread();
        System.out.println("main方法執行緒" + thread.getName());
    }
}

5、執行緒的優先順序

  • setPriority(int newPriority) 設定執行緒優先順序
  • final int getPriority() 獲取執行緒優先順序

執行緒類MyRunnable

public class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "===" + i);
        }
    }
}

執行類ThreadDemo

public class ThreadDemo {

    public static void main(String[] args) {
        /*
         * setPriority(int newPriority)  設定執行緒優先順序
         * final int getPriority() 獲取執行緒優先順序
         *
         */
        // 建立執行緒要執行的引數物件
        MyRunnable mr = new MyRunnable();
        // 建立執行緒物件
        Thread t1 = new Thread(mr, "飛機");
        Thread t2 = new Thread(mr, "坦克");

        // 設定優先順序
        t1.setPriority(1);
        t2.setPriority(10);

        // 檢視執行緒優先順序
        System.out.println(t1.getPriority());
        System.out.println(t2.getPriority());

        // 啟動執行緒
        t1.start();
        t2.start();
    }
}

6、守護執行緒

final void setDaemon(boolean on) 設定為守護執行緒(備胎執行緒),當其他的非守護執行緒執行結束後,守護執行緒會陸續結束,當非守護執行緒結束後,守護執行緒就沒有存在的必要了。

執行緒類MyThread1,MyThread2

public class MyThread1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "@" + i);
        }
    }
}
public class MyThread2 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "@" + i);
        }
    }
}

執行類ThreadDemo

public class ThreadDemo {

    public static void main(String[] args) {

        MyThread1 t1 = new MyThread1();
        MyThread2 t2 = new MyThread2();

        t1.setName("女神");
        t2.setName("備胎");

        // 執行緒2設定為守護執行緒
        t2.setDaemon(true);

        t1.start();
        t2.start();
    }
}

7、執行緒的讓出

public static void yield() 出讓執行緒/禮讓執行緒,讓出當前執行執行緒CPU的執行權

執行緒類MyThread

public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "@" + i);
            // 出讓當前CPU的執行權
            Thread.yield();
        }
    }
}

執行類ThreadDemo

public class ThreadDemo {
    public static void main(String[] args) {

        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.setName("飛機");
        t2.setName("坦克");

        t1.start();
        t2.start();
    }
}

8、執行緒插隊

public final void join() 插入執行緒/插隊執行緒,講指定的執行緒插入到main(當前執行緒)之前執行

執行緒類MyThread

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "@" + i);
        }
    }
}

執行類ThreadDemo

public class ThreadDemo {

    public static void main(String[] args) throws InterruptedException {

        MyThread t1 = new MyThread();
        t1.setName("土豆");
        t1.start();
        // 將t1(土豆) 執行緒插入到main執行緒(當前執行緒)之前
        t1.join();

        // 執行在main執行緒中
        for (int i = 0; i < 10; i++) {
            System.out.println("main執行緒" + i);
        }
    }
}

9、同步程式碼塊

在需要同步的程式碼塊中加入synchronized(當前類位元組碼檔案)

執行緒類MyThread

public class MyThread extends Thread {

    /**
     * 票號,所有的類都共享該票號
     */
    static int ticket = 0;

    @Override
    public void run() {
        while (true) {
            // 使用同步程式碼塊枷鎖,鎖物件必須是唯一的才行(使用當前類位元組碼)
            synchronized (MyThread.class) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                if (ticket < 100) {
                    ticket++;
                    System.out.println(getName() + "正在賣第" + ticket + "張票!");
                } else {
                    break;
                }
            }
        }
    }
}

執行類ThreadDemo

public class ThreadDemo {

    public static void main(String[] args) {

        /*
         * 需求
         *       某電影院有100張票,只有三個買票視窗,使用多執行緒設計一個模擬程式來賣票
         * 利用同步程式碼塊來完成
         */

        // 建立執行緒物件
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

        // 起名字
        t1.setName("視窗1");
        t2.setName("視窗2");
        t3.setName("視窗3");

        // 開啟執行緒
        t1.start();
        t2.start();
        t3.start();
    }
}

10、同步方法

將需要同步的程式碼抽取為一個方法,並使用synchronized進行修飾

執行緒類MyRunnable

public class MyRunnable implements Runnable {

    int ticket = 0;

    @Override
    public void run() {
        // 1.寫迴圈
        while (true) {
            // 2.同步程式碼塊(同步方法)
            if (method()) {
                break;
            }
        }
    }

    // 3.同步方法(從同步程式碼塊中抽取出來)
    private synchronized boolean method() {
        // 4.共享程式碼是否到了末尾
        if (ticket == 1000) {
            return true;
        } else {
            ticket++;
            System.out.println(Thread.currentThread().getName() + "在賣第" + ticket + "張票");
        }
        return false;
    }
}

執行類ThreadDemo

public class ThreadDemo {

    public static void main(String[] args) {

        /*
         * 需求
         *       某電影院有100張票,只有三個買票視窗,使用多執行緒設計一個模擬程式來賣票
         *  利用同步方法來完成
         */

        MyRunnable mr = new MyRunnable();

        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        Thread t3 = new Thread(mr);

        t1.setName("視窗1");
        t2.setName("視窗2");
        t3.setName("視窗3");

        t1.start();
        t2.start();
        t3.start();

    }
}

11、執行緒鎖

lock() 配合使用可以達到synchronized相同操作

執行緒類MyThread

public class MyThread extends Thread {

    static int ticket = 0;

    // 只有一把鎖
    static Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            // 2.同步程式碼塊
            // synchronized (MyThread.class) {
            lock.lock(); // 加鎖
            // 3.判斷
            try {
                if (ticket == 100) {
                    break;
                } else {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket++;
                    System.out.println(getName() + "在賣第" + ticket + "張票");
                }
            }finally {
                lock.unlock(); // 釋放鎖
            }
            //   }
        }

    }
}

執行類ThreadDemo

public class ThreadDemo {
    public static void main(String[] args) {

        /*
         * 需求:
         *      某電影院目前正在上映國產電影,共有100張票,3個視窗
         *      使用JDK的lock實現
         */
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

        t1.setName("視窗1");
        t2.setName("視窗2");
        t3.setName("視窗3");

        t1.start();
        t2.start();
        t3.start();
    }
}

12、死鎖問題

死鎖原因常見於鎖的巢狀等操作,導致相互獲取不到資源產生的等待問題

執行緒類MyThread

public class MyThread extends Thread{

    static Object objA = new Object();
    static Object objB = new Object();

    @Override
    public void run() {
        // 1.迴圈
        while (true){
            if ("執行緒A".equals(getName())){
                synchronized (objA){
                    System.out.println("執行緒A拿到了A鎖,準備拿B鎖");
                    synchronized (objB){
                        System.out.println("執行緒A拿到了B");
                    }
                }
            }else if ("執行緒B".equals(getName())){
                if ("執行緒B".equals(getName())){
                    synchronized (objB){
                        System.out.println("執行緒B拿到了B鎖,準備拿A鎖");
                        synchronized (objA){
                            System.out.println("執行緒B拿到了A鎖,順利執行完了一輪");
                        }
                    }
                }
            }
        }
    }
}

執行類ThreadDemo

public class ThreadDemo {

    public static void main(String[] args) {
        /*
         * 需求:死鎖
         */
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.setName("執行緒A");
        t2.setName("執行緒B");

        t1.start();
        t2.start();
    }
}

13、等待喚醒機制(消費者模式)

生產者(Cook) => 中間者(Desk) <= 消費者(Foodie)

執行緒類Cook,Foodie

/**
* 廚師
*/
public class Cook extends Thread {

    /**
     * 1.迴圈
     * 2.同步程式碼塊
     * 3.判斷共享資料是否到末尾(到了)
     * 4.判斷共享資料是否到末尾(沒有,執行核心邏輯)
     */

    @Override
    public void run() {
        // 1.迴圈
        while (true) {
            // 2.同步程式碼塊
            synchronized (Desk.lock) {
                // 3.判斷共享資料是否到末尾(到了)
                if (Desk.count == 0) {
                    break;
                } else {
                    // 4.判斷共享資料是否到末尾(沒有,執行核心邏輯)

                    // 判斷桌子上是否有食物
                    if (Desk.foodFlag == 1) {
                        // 如果有 就等待
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else {
                        // 如果沒有 就製作
                        System.out.println("廚師做了一碗麵條");
                        // 修改桌子上食物狀態
                        Desk.foodFlag = 1;
                        // 叫醒等待的消費者開吃
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}
/**
* 客人
*/
public class Foodie extends Thread {

    /**
     * 1.迴圈
     * 2.同步程式碼塊
     * 3.判斷共享資料是否到末尾(到了)
     * 4.判斷共享資料是否到末尾(沒有,執行核心邏輯)
     */

    @Override
    public void run() {

        // 1.迴圈
        while (true) {
            // 2.同步程式碼塊
            synchronized (Desk.lock) {
                // 3.判斷共享資料是否到了末尾(到了末尾,執行緒執行完畢)
                if (Desk.count == 0) {
                    break;
                } else {
                    // 4.判斷共享資料是否到了末尾(沒有到末尾,執行核心邏輯)
                    // 先判斷桌子上是否有面條
                    if (Desk.foodFlag == 0) {
                        // 沒有就等待
                        try {
                            Desk.lock.wait(); // 讓當前執行緒跟鎖進行繫結
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    } else {
                        // 把吃的總數減一
                        Desk.count--;
                        // 有就開吃
                        System.out.println("吃貨正在吃麵條,還能吃" + Desk.count + "碗!");
                        // 吃完後喚醒廚師繼續製作
                        Desk.lock.notifyAll();
                        // 修改桌子狀態
                        Desk.foodFlag = 0;
                    }
                }
            }
        }
    }
}
/**
* 中間者 桌子
*/
public class Desk {
    /*
     * 控制生產者和消費者的執行
     */

    /**
     * 桌子上是否有面條 0:沒有 1:有
     */
    public static int foodFlag = 0;

    /**
     * 總個數,最多能吃10碗
     */
    public static int count = 10;

    // 鎖物件
    public static Object lock = new Object();

}

執行類ThreadDemo

public class ThreadDemo {

    public static void main(String[] args) {
        /*
         * 需求:完成生產者和消費者(等待喚醒機制)的程式碼
         *      實現執行緒輪流交替的執行效果
         *
         * 生產者 ===> 中間者 <=== 消費者
         *
         */

        // 建立執行緒物件
        Cook cook = new Cook();
        Foodie foodie = new Foodie();

        cook.setName("廚師");
        foodie.setName("客人");

        // 開啟執行緒
        cook.start();
        foodie.start();

    }
}

14、阻塞佇列下的等待喚醒機制

生產者和消費者必須使用同一個佇列ArrayBlockingQueue

生產者 => ArrayBlockingQueue <= 消費者

執行緒類Cook,Foodie

/**
* 廚師
*/
public class Cook extends Thread {

    ArrayBlockingQueue<String> queue;

    public Cook(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }


    @Override
    public void run() {

        while (true) {
            // 不斷的把麵條放入阻塞佇列中
            try {
                // put方法底層實現了鎖操作,所以無需加鎖
                queue.put("麵條");
                System.out.println("廚師放了一碗麵條");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
/**
* 客人
*/
public class Foodie extends Thread {

    ArrayBlockingQueue<String> queue;

    public Foodie(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            // 不斷的從阻塞佇列中獲取麵條
            // peek方法底層實現了鎖操作,所以無需加鎖
            String food = null;
            try {
                food = queue.take();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("客戶吃了 " + food);
        }
    }
}

執行類ThreadDemo

public class ThreadDemo {

    public static void main(String[] args) {

        /*
         * 需要:利用阻塞佇列完成生產者和消費者(等待喚醒機制)的程式碼
         * 細節:
         *      生產者和消費者必須使用同一個佇列
         */

        // 1.建立阻塞佇列(容量位1 )
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);

        // 2.建立執行緒物件,並把阻塞佇列傳遞過去
        Cook cook = new Cook(queue);
        Foodie foodie = new Foodie(queue);

        // 3.開啟執行緒
        cook.start();
        foodie.start();
    }
}

執行緒池

1、執行緒池的建立

ExecutorService newCachedThreadPool() 建立一個沒有上限的執行緒池

ExecutorService new FixedThreadPool(int nThread) 建立有上限的執行緒池

執行緒類MyRunnable

public class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

執行類MyThreadPoolDemo

public class MyThreadPoolDemo {

    public static void main(String[] args) throws InterruptedException {

        /*
         * public static ExecutorService newCachedThreadPool() 建立一個沒有上限的執行緒池
         * public static ExecutorService new FixedThreadPool(int nThread) 建立有上限的執行緒池
         */

        fixedThreadPool();
    }

    public static void newCachedThreadPool() throws InterruptedException {
        // 1.獲取執行緒池物件
        ExecutorService pool1 = Executors.newCachedThreadPool();

        // 2.提交任務
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());


        // 3.銷燬執行緒池
        pool1.shutdown();
    }

    public static void fixedThreadPool() throws InterruptedException {
        // 建立三個執行緒
        ExecutorService pool1 = Executors.newFixedThreadPool(3);
        // 2.提交任務
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());

        // 3.銷燬執行緒池
        pool1.shutdown();
    }
}

2、自定義執行緒池

(核心執行緒數量, 最大執行緒數量, 空閒執行緒最大存活時間, 任務佇列, 建立執行緒工廠, 任務的拒絕策略)
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
	引數一: 核心執行緒數量      不能小於0,
	引數二: 最大執行緒數       不能小於等於0,最大數量 >= 核心執行緒數量,
	引數三: 控線執行緒最大存活時間      不能小於0,
	引數四: 執行緒存活時間單位        用TimeUnit指定,
	引數五: 任務佇列        不能為null,
	引數六: 建立執行緒工廠      不能為null,
	引數七: 任務的拒絕策略     不能為null
);

例子,MyThreadPoolDemo1

public class MyThreadPoolDemo1 {

    public static void main(String[] args) {
        // 自定義執行緒池
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                // 核心執行緒數量,不能小於0
                3,
                // 最大執行緒數不能小於等於0,最大數量 >= 核心執行緒數量
                6,
                // 控線執行緒最大存活時間
                60,
                // 執行緒存活時間單位,用TimeUnit指定
                TimeUnit.SECONDS,
                // 任務佇列
                new ArrayBlockingQueue<>(3),
                // 建立執行緒工廠
                Executors.defaultThreadFactory(),
                //  任務的拒絕策略
                new ThreadPoolExecutor.CallerRunsPolicy()
        );

        // 提交任務到執行緒池
        pool.submit(() -> {
            System.out.println(Thread.currentThread().getName());
        });
    }
}

相關文章