【Java多執行緒】輕鬆搞定Java多執行緒(二)

Black-Panda發表於2020-10-28

Java 多執行緒詳解(二)

1、執行緒狀態

1.1 執行緒狀態

1

1.2 執行緒方法

方法說明
setPriority(int newPriority)更改執行緒的優先順序
static void sleep(long millis)在指定的毫秒級內讓當前正在執行的執行緒休眠
void join()等待該執行緒終止
static void yield()暫停當前正在執行的執行緒物件,並執行其他執行緒
void interrupt()中斷執行緒(不建議使用)
boolean isAlive()測試執行緒是否處於活動狀態

停止執行緒

  • 不推薦使用JDK提供的stop()、destroy()方法。(已廢棄)
  • 推薦執行緒自己停下來
  • 建議使用一個標誌位進行終止變數,當flag=false,則終止執行緒執行
/**
 * 測試stop
 * 1.建議執行緒正常停止--->利用次數,不建議死迴圈
 * 2.建議使用標誌位--->設定一個標誌位
 * 3.不要使用stop()或destroy()等過時,或者JDk不建議使用的方法
 */
public class TestStop implements Runnable {

    /**
     * 1.設定一個標誌位
     */
    private boolean flag = true;

    @Override
    public void run() {
        int i = 0;
        while (flag) {
            System.out.println("run...Thread" + i++);
        }
    }

    /**
     * 2.設定一個公開的方法停止執行緒,轉換標誌位
     */
    public void stop() {
        this.flag = false;
    }

    public static void main(String[] args) {
        TestStop testStop = new TestStop();
        new Thread(testStop).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("main" + i);
            if (i == 900) {
                /**
                 * 呼叫stop()方法,切換標誌位,讓執行緒停止
                 */
                testStop.stop();
                System.out.println("執行緒停止了");
            }
        }
    }
}

執行緒休眠

  • sleep(時間)指定當前執行緒阻塞的毫秒數;
  • sleep存在異常InterruptedException;
  • sleep時間達到後執行緒進入就緒狀態;
  • sleep可以模擬網路延時,倒數計時等;
  • 每一個物件都有一個鎖,sleep不會釋放鎖。
模擬網路延時
/**
 * 模擬網路延時:放大問題的發生性
 */
public class TestSleep implements Runnable {
    /**
     * 票數
     */
    private int ticketsNums = 20;

    @Override
    public void run() {
        while (true) {
            if (ticketsNums <= 0) {
                break;
            }

            /**
             * 模擬延時
             */
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "拿到了" + ticketsNums-- + "票");
        }
    }

    public static void main(String[] args) {
        TestSleep ticket = new TestSleep();

        new Thread(ticket, "小明").start();
        new Thread(ticket, "老師").start();
        new Thread(ticket, "黃牛").start();
    }
}
模擬倒數計時
/**
 * 模擬倒數計時
 */
public class TestSleep2 {

    public static void main(String[] args) {
        try {
            tenDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void tenDown() throws InterruptedException {
        int num = 10;

        while (true) {
            Thread.sleep(1000);
            System.out.println(num--);
            if (num <= 0) {
                break;
            }
        }
    }
}

執行緒禮讓

  • 禮讓執行緒,讓當前正在執行的執行緒暫停,但不阻塞
  • 將執行緒從執行狀態轉為就緒狀態
  • 讓CPU重新排程,禮讓不一定成功!看CPU心情
/**
 * 測試禮讓執行緒
 * 禮讓不一定成功
 */
public class TestYield {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();
        new Thread(myYield, "a").start();
        new Thread(myYield, "b").start();
    }
}

class MyYield implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "執行緒開始執行");
        /**
         * 禮讓
         */
        Thread.yield();
        System.out.println(Thread.currentThread().getName() + "執行緒停止執行");
    }
}

Join

  • Join合併執行緒,待此執行緒執行完成後,再執行其他執行緒,其他執行緒阻塞
  • 可以想象成插隊
/**
 * 測試join方法
 */
public class TestJoin implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("執行緒vip來了" + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        /**
         * vip執行緒
         */
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);
        thread.start();

        /**
         * 主執行緒
         */
        for (int i = 0; i < 1000; i++) {
            if (i == 200) {
                thread.join();
            }
            System.out.println("main" + i);
        }
    }
}

1.3 執行緒狀態觀測

image-20200908200728290

/**
 * 觀察測試執行緒的狀態
 */
public class TestState {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("///");
        });

        /**
         * 觀察狀態
         */
        Thread.State state = thread.getState();
        System.out.println(state);

        /**
         * 啟動執行緒
         */
        thread.start();
        state = thread.getState();
        System.out.println(state);

        /**
         * 只要執行緒不終止,就一直輸出狀態
         */
        while (state != Thread.State.TERMINATED) {
            Thread.sleep(100);
            state = thread.getState();
            System.out.println(state);
        }
    }

}

1.4 執行緒優先順序

/**
 * 測試執行緒優先順序
 */
public class TestPriority {

    public static void main(String[] args) {
        /**
         * 主執行緒預設優先順序
         */
        System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());

        MyPriority myPriority = new MyPriority();

        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);

        /**
         * 先設定優先順序,再啟動
         */
        t2.setPriority(1);
        t3.setPriority(4);
        t4.setPriority(Thread.MAX_PRIORITY);

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

}

class MyPriority implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
    }
}

1.5 守護執行緒

  • 執行緒分為使用者執行緒守護執行緒
  • 虛擬機器必須確保使用者執行緒執行完畢
  • 虛擬機器不用等待守護執行緒執行完畢
  • 如:後臺記錄操作日誌,監控記憶體,垃圾回收等等…
/**
 * 測試守護執行緒
 * 上帝守護
 */
public class TestDaemon {

    public static void main(String[] args) {
        God god = new God();
        Human human = new Human();

        Thread thread = new Thread(god);
        /**
         * 預設false表示使用者執行緒,正常的執行緒都是使用者執行緒
         */
        thread.setDaemon(true);

        thread.start();

        /**
         * 使用者執行緒啟動
         */
        new Thread(human).start();
    }
}

/**
 * 上帝
 */
class God implements Runnable {

    @Override
    public void run() {
        while (true) {
            System.out.println("上帝守護");
        }
    }
}


/**
 * 人
 */
class Human implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("活著");
        }
        System.out.println("======離開======");
    }
}

2、執行緒同步

多個執行緒操作同一個資源

2.1 併發

  • 併發:同一個物件多個執行緒同時操作

2.2 執行緒同步

  • 處理多執行緒問題時,多個執行緒訪問同一個人物件(併發問題),並且某些執行緒還想修改這個物件。這個時候我們就需要執行緒同步。
  • 執行緒同步其實就是一種等待機制,多個需要同時訪問此物件的執行緒進入這個物件的等待池形成佇列,等待前面執行緒使用完畢,下一個執行緒再使用。
  • 形成條件:佇列和鎖
  • 由於同一程式的多個執行緒共享同一塊儲存空間,在帶來方便的同時,也帶來了訪問衝突問題,為了保證資料在方法中被訪問的正確性,在訪問時加入鎖機制(synchronized),當一個執行緒獲得物件的排它鎖,獨佔資源,其他執行緒必須等待,使用後釋放鎖即可。存在以下問題:
    • 一個執行緒持有鎖會導致其他所有需要此鎖的執行緒掛起
    • 在多執行緒競爭下,加鎖,釋放鎖會導致比較多的上下文切換和排程延時,引起效能問題
    • 如果一個優先順序高的執行緒等待一個優先順序低的執行緒釋放鎖,會導致優先順序倒置,引起效能問題

不安全的買票

/**
 * 不安全的買票
 */
public class UnsafeBuyTicket {

    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();

        new Thread(station, "小明").start();
        new Thread(station, "小紅").start();
        new Thread(station, "小黑").start();

    }

}

class BuyTicket implements Runnable {

    /**
     * 票
     */
    private int ticketNums = 10;

    /**
     * 外部停止方式
     */
    boolean flag = true;

    @Override
    public void run() {
        /**
         * 買票
         */
        while (flag) {
            buy();
        }

    }

    private void buy() {
        /**
         * 判斷是否有票
         */
        if (ticketNums <= 0) {
            flag = false;
            return;
        }

        /**
         * 模擬延遲
         */
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + "拿到" + ticketNums--);
    }
}

不安全的取錢

/**
 * 不安全的取錢
 * 兩個人用同一賬號去銀行取錢
 */
public class UnsafeBank {

    public static void main(String[] args) {
        Account account = new Account(100, "錢");

        Drawing Jack = new Drawing(account, 50, "Jack");
        Drawing Rose = new Drawing(account, 100, "Rose");

        Jack.start();
        Rose.start();
    }


}

/**
 * 賬戶
 */
class Account {
    int money;
    String name;

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}

/**
 * 銀行:模擬取款
 */
class Drawing extends Thread {
    /**
     * 賬戶
     */
    Account account;
    /**
     * 取了多少錢
     */
    int drawingMoney;
    /**
     * 現在手裡有多少錢
     */
    int nowMoney;

    public Drawing(Account account, int drawingMoney, String name) {
        this.account = account;
        this.drawingMoney = drawingMoney;
        super.setName(name);
    }

    /**
     * 取錢
     */
    @Override
    public void run() {
        /**
         * 判斷有沒有錢
         */
        if (account.money - drawingMoney < 0) {
            System.out.println(Thread.currentThread().getName() + "餘額不足");
            return;
        }

        /**
         * 模擬延時
         */
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        /**
         * 卡內餘額
         */
        account.money = account.money - drawingMoney;

        /**
         * 手裡的錢
         */
        nowMoney = nowMoney + drawingMoney;

        System.out.println(account.name + "餘額為:" + account.money);
        System.out.println(this.getName() + "手裡的錢:" + nowMoney);

    }
}

執行緒不安全的集合

/**
 * 執行緒不安全的集合
 */
public class UnsafeList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

同步方法

  • 由於我們可以通過private關鍵字來保證資料物件只能被方法訪問,所以我們只需要針對方法提出一套機制,這套機制就是synchronized關鍵字,它包括兩種用法:
    • synchronized方法
    • synchronized塊
    /**
     * 同步方法
     */
    public synchronized void method(int args) {}
  • synchronized方法控制對“物件”的訪問,每一個物件對應一把鎖,每個synchronized方法都必須獲得呼叫該方法的物件的鎖才能執行,否則執行緒會阻塞,方法一旦執行,就獨佔該鎖,直到該方法返回才釋放鎖,後面被阻塞的執行緒才能獲得這個鎖,繼續執行

同步方法的弊端:

  • 方法裡面需要修改的內容才需要鎖,鎖得太多,浪費資源
  • 若將一個大的方法宣告為synchronized將會影響效率

安全的買票

/**
 * 安全的買票
 */
public class SafeBuyTicket {

    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();

        new Thread(station, "小明").start();
        new Thread(station, "小紅").start();
        new Thread(station, "小黑").start();
    }

}

class BuyTicket implements Runnable {

    /**
     * 票
     */
    private int ticketNums = 10;

    /**
     * 外部停止方式
     */
    boolean flag = true;

    @Override
    public void run() {
        /**
         * 買票
         */
        while (flag) {
            /**
             * 模擬延遲
             */
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            buy();
        }

    }

    /**
     * synchronized 同步方法,鎖的是this
     */
    private synchronized void buy() {
        /**
         * 判斷是否有票
         */
        if (ticketNums <= 0) {
            flag = false;
            return;
        }

        /**
         * 模擬延遲
         */
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + "拿到" + ticketNums--);
    }
}

同步塊

  • 同步塊:synchronized(Obj) {}
  • Obj稱之為同步監視器
    • Obj可以是任何物件,但是推薦使用共享資源作為同步監視器
    • 同步方法中無需指定同步監視器,因為同步方法的同步監視器就是this,就是這個物件本身,或者是class
  • 同步監視器的執行過程
    1. 第一個執行緒訪問,鎖定同步監視器,執行其中程式碼
    2. 第二個執行緒訪問,發現同步監視器被鎖定,無法訪問
    3. 第一個執行緒訪問完畢,解鎖同步監視器
    4. 第二個執行緒訪問,發現同步監視器沒有鎖,然後鎖定訪問

安全的取錢

/**
 * 安全的取錢
 * 兩個人用同一賬號去銀行取錢
 */
public class SafeBank {

    public static void main(String[] args) {
        Account account = new Account(100, "錢");

        Drawing Jack = new Drawing(account, 50, "Jack");
        Drawing Rose = new Drawing(account, 100, "Rose");

        Jack.start();
        Rose.start();
    }


}

/**
 * 賬戶
 */
class Account {
    int money;
    String name;

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}

/**
 * 銀行:模擬取款
 */
class Drawing extends Thread {
    /**
     * 賬戶
     */
    Account account;
    /**
     * 取了多少錢
     */
    int drawingMoney;
    /**
     * 現在手裡有多少錢
     */
    int nowMoney;

    public Drawing(Account account, int drawingMoney, String name) {
        this.account = account;
        this.drawingMoney = drawingMoney;
        super.setName(name);
    }

    /**
     * 取錢
     * synchronized預設鎖this
     */
    @Override
    public void run() {

        /**
         * 同步塊
         * 鎖的物件就是變化的量,需要增刪改的物件
         */
        synchronized (account) {
            /**
             * 判斷有沒有錢
             */
            if (account.money - drawingMoney < 0) {
                System.out.println(Thread.currentThread().getName() + "餘額不足");
                return;
            }

            /**
             * 模擬延時
             */
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            /**
             * 卡內餘額
             */
            account.money = account.money - drawingMoney;

            /**
             * 手裡的錢
             */
            nowMoney = nowMoney + drawingMoney;

            System.out.println(account.name + "餘額為:" + account.money);
            System.out.println(this.getName() + "手裡的錢:" + nowMoney);
        }
    }
}

執行緒安全的集合

/**
 * 執行緒安全的集合
 */
public class SafeList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                /**
                 * 同步塊
                 */
                synchronized (list) {
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

JUC安全型別的集合

import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 測試JUC安全型別的集合
 */
public class TestJUC {

    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

2.3 死鎖

  • 多個執行緒各自佔有一些共享資源,並且互相等待其他執行緒佔有的資源才能執行,而導致兩個或者多個執行緒都在等待對方釋放資源,都停止執行的情形。某一個同步塊同時擁有“兩個以上物件的鎖”時,就可能會發生“死鎖”的問題。
/**
 * 死鎖:多個執行緒互相抱著對方需要的資源,然後形成僵持
 */
public class DeadLock {

    public static void main(String[] args) {
        Makeup g1 = new Makeup(0, "一號");
        Makeup g2 = new Makeup(1, "二號");

        g1.start();
        g2.start();
    }

}

/**
 * 口紅
 */
class Lipstick {}

/**
 * 鏡子
 */
class Mirror {}

class Makeup extends Thread {

    /**
     * 需要的資源只有一份,用static來保證只有一份
     */
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    int choice; // 選擇
    String name; // 使用化妝品的人

    Makeup(int choice, String name) {
        this.choice = choice;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 化妝,互相持有對方的鎖,就是需要拿到對方的資源
     */
    private void makeup() throws InterruptedException {
        if (choice == 0) {
            /**
             * 獲得口紅的鎖
             */
            synchronized (lipstick) {
                System.out.println(this.name + "獲得口紅的鎖");
                Thread.sleep(1000);

                /**
                 * 一秒鐘後想獲得鏡子
                 */
                synchronized (mirror) {
                    System.out.println(this.name + "獲得鏡子的鎖");
                }
            }
        } else {
            /**
             * 獲得鏡子的鎖
             */
            synchronized (mirror) {
                System.out.println(this.name + "獲得鏡子的鎖");
                Thread.sleep(2000);

                /**
                 * 兩秒鐘後想獲得口紅
                 */
                synchronized (lipstick) {
                    System.out.println(this.name + "獲得口紅的鎖");
                }
            }
        }
    }
}

死鎖避免方法

  • 產生死鎖的四個必要條件:
    1. 互斥條件:一個資源每次只能被一個程式使用。
    2. 請求與保持條件:一個程式因請求資源而阻塞時,對已獲得的資源保持不放。
    3. 不剝奪條件:程式已獲得的資源,在未使用完之前,不能強行剝奪。
    4. 迴圈等待條件:若干程式之間形成一種頭尾相接的迴圈等待資源關係。

2.4 鎖

  • 從JDK5.0開始,Java提供了更強大的執行緒同步機制——通過顯式定義同步鎖物件來實現同步。同步鎖使用Lock物件充當。
  • java.util.concurrent.locks.Lock介面是控制多個執行緒對共享資源進行訪問的工具。鎖提供了對共享資源的獨佔訪問,每次只能有一個執行緒對Lock物件加鎖,執行緒開始訪問共享資源之前應先獲得Lock物件。
  • ReentrantLock(可重入鎖)類實現了Lock,它擁有與synchronized相同的併發性和記憶體語義,在實現執行緒安全的控制中,比較常用的是ReentrantLock,可以顯式加鎖、釋放鎖。
import java.util.concurrent.locks.ReentrantLock;

/**
 * 測試Lock鎖
 */
public class TestLock {
    public static void main(String[] args) {
        TestLock2 testLock2 = new TestLock2();

        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
    }
}

class TestLock2 implements Runnable {

    int ticketNums = 10;

    /**
     * 定義lock鎖
     */
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (ticketNums > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNums--);
                } else {
                    break;
                }
            } finally {
                lock.unlock();
            }
        }
    }
}

synchronized與Lock的對比

  • Lock是顯式鎖(手動開啟和關閉鎖),synchronized是隱式鎖,出了作用域自動釋放。
  • Lock只有程式碼塊鎖,synchronized有程式碼塊鎖和方法鎖。
  • 使用Lock鎖,JVM將花費較少的時間來排程執行緒,效能更好。並且具有更好的擴充套件性(提供更多的子類)。
  • 優先使用順序:
    • Lock > 同步程式碼塊(已經進入了方法體,分配了相應的資源) > 同步方法(在方法體之外)

3、執行緒協作

3.1 生產者消費者問題

  • 應用場景:
    • 假設倉庫中只能存放一件產品,生產者將生產出來的產品放入倉庫,消費者將倉庫中產品取走消費。
    • 如果倉庫中沒有產品,則生產者將產品放入倉庫,否則停止生產並等待,直到倉庫中的產品被消費者取走為止。
    • 如果倉庫中放有產品,則消費者可以將產品取走消費,否則停止消費並等待,直到倉庫中再次放入產品為止。

這是一個執行緒同步問題,生產者和消費者共享同一個資源,並且生產者和消費者之間相互依賴,互為條件。

在生產者消費者問題中,僅有synchronized是不夠的

  • synchronized可阻止併發更新同一個共享資源,實現了同步
  • synchronized不能用來實現不同執行緒之間的訊息傳遞(通訊)

3.2 執行緒通訊

  • Java提供了幾個方法解決執行緒之間的通訊問題
方法名作用
wait()表示執行緒一直等待,直到其他執行緒通知,與sleep不同,會釋放鎖
wait(long timeout)指定等待的毫秒數
notify()喚醒一個處於等待狀態的執行緒
notifyAll()喚醒同一個物件上所有呼叫wait()方法的執行緒,優先順序高的執行緒優先排程

注意:

均是Object類的方法,都只能在同步方法或者同步程式碼塊中使用,否則會丟擲異常IllegalMonitorStateException

3.3 管理法

/**
 * 測試:生產者消費者模型-->利用緩衝區解決:管理法
 */
public class TestPC {

    public static void main(String[] args) {
        SynContainer container = new SynContainer();

        new Producer(container).start();
        new Consumer(container).start();
    }

}

/**
 * 生產者
 */
class Producer extends Thread {
    SynContainer container;

    public Producer(SynContainer container) {
        this.container = container;
    }
    /**
     * 生產
     */
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("生產了第" + i + "只雞");
            container.push(new Chicken(i));
        }
    }
}

/**
 * 消費者
 */
class Consumer extends Thread {
    SynContainer container;

    public Consumer(SynContainer container) {
        this.container = container;
    }

    /**
     * 消費
     */
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消費了第" + i + "只雞");
            container.pop();
        }
    }
}

/**
 * 產品
 */
class Chicken {
    /**
     * 產品編號
     */
    int id;

    public Chicken(int id) {
        this.id = id;
    }
}

/**
 * 緩衝區
 */
class SynContainer {
    /**
     * 需要一個容器大小
     */
    Chicken[] chickens = new Chicken[10];

    /**
     * 容器計數器
     */
    int count = 0;

    /**
     * 生產者生產產品
     */
    public synchronized void push(Chicken chicken) {
        /**
         * 如果容器滿了,就需要等待消費者消費
         */
        if (count == chickens.length) {
            /**
             * 通知消費者消費,生產等待
             */
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        /**
         * 如果沒有滿,就放入產品
         */
        chickens[count] = chicken;
        count++;

        /**
         * 通知消費者消費
         */
        this.notifyAll();
    }

    /**
     * 消費者消費產品
     */
    public synchronized Chicken pop() {
        /**
         * 判斷能否消費
         */
        if (count == 0) {
            /**
             * 等待生產者生產,消費者等待
             */
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        /**
         * 如果可以消費
         */
        count--;
        Chicken chicken = chickens[count];

        /**
         * 消費完通知生產者生產
         */
        this.notifyAll();
        return chicken;
    }
}

3.4 訊號燈法

/**
 * 測試生產者消費者問題:訊號燈法
 */
public class TestPC2 {
    public static void main(String[] args) {
        Film film = new Film();
        new Player(film).start();
        new Watcher(film).start();
    }

}

/**
 * 生產者-->演員
 */
class Player extends Thread {
    Film film;
    public Player(Film film) {
        this.film = film;
    }

    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            if (i%2 == 0) {
                this.film.play("盜夢空間");
            } else {
                this.film.play("星際穿越");
            }
        }
    }
}

/**
 * 消費者-->觀眾
 */
class Watcher extends Thread {
    Film film;
    public Watcher(Film film) {
        this.film = film;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            film.watch();
        }
    }
}

/**
 * 產品-->電影
 */
class Film {
    /**
     * 演員演戲,觀眾等待    T
     * 觀眾觀看,演員等待    F
     */

    /**
     * 表演的電影
     */
    String name;
    boolean flag = true;

    public synchronized void play(String name) {

        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("演員表演了:"+name);
        /**
         * 通知觀眾觀看
         */
        this.notifyAll();
        this.name = name;
        this.flag = !this.flag;
    }

    /**
     * 觀眾觀看
     */
    public synchronized void watch() {
        if(flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("觀看了:"+name);

        /**
         * 通知演員演戲
         */
        this.notifyAll();
        this.flag = !this.flag;
    }
}

4、執行緒池

  • 背景:經常建立和銷燬、使用量特別大的資源,比如併發情況下的執行緒,對效能影響很大。
  • 思路:提前建立好多個執行緒,放入執行緒池中,使用時直接獲取,使用完返回池中。可以避免頻繁建立銷燬、實現重複利用。類似生活中的公共交通工具。
  • 好處:
    • 提高響應速度(減少了建立新執行緒的時間)
    • 降低資源消耗(重複利用執行緒池中執行緒,不需要每次都建立)
    • 便於執行緒管理
      • corePoolSixe:核心池的大小
      • maximumPoolSize:最大執行緒數
      • keepAliveTime:執行緒沒有任務時最多保持多長時間後會終止

使用執行緒池

  • JDK5.0起提供了執行緒池相關API:ExecutorService和Executors
  • ExecutorService:真正的執行緒池介面。常見子類ThreadPoolExecutor
    • void execute(Runnable command):執行任務/命令,沒有返回值,一般用來執行Runnable
    • < T >Future< T > submit(Callable< T > task):執行任務,有返回值,一般用來執行Callable
    • void shutdown():關閉執行緒池
  • Executors:工具類、執行緒類的工廠類,用於建立並返回不同型別的執行緒池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 測試執行緒池
 */
public class TestPool {
    public static void main(String[] args) {
        /**
         * 1.建立服務
         * newFixedThreadPool   引數為:執行緒池大小
         */
        ExecutorService service = Executors.newFixedThreadPool(10);

        /**
         * 執行
         */
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        /**
         * 2.關閉服務
         */
        service.shutdown();

    }
}

class MyThread implements Runnable {

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

相關文章