Java多線程

来源:https://www.cnblogs.com/yliucnblogs/archive/2019/04/07/10668112.html
-Advertisement-
Play Games

什麼是多線程 利用對象,可將一個程式分割成相互獨立的區域。我們通常也需要將一個程式轉換成多個獨立運行的子任 務。象這樣的每個子任務都叫作一個“線程”(Thread)。編寫程式時,可將每個線程都想象成獨立運行,而且 都有自己的專用CPU。一些基礎機制實際會為我們自動分割CPU的時間。我們通常不必關心這 ...


什麼是多線程

利用對象,可將一個程式分割成相互獨立的區域。我們通常也需要將一個程式轉換成多個獨立運行的子任
務。象這樣的每個子任務都叫作一個“線程”(Thread)。編寫程式時,可將每個線程都想象成獨立運行,而且
都有自己的專用CPU。一些基礎機制實際會為我們自動分割CPU的時間。我們通常不必關心這些細節問題,
所以多線程的代碼編寫是相當簡便的。引自《Java編程思想》第四版

生命周期

在這裡插入圖片描述

如何開啟一個線程

  • 繼承Thread

Thread類本質上是實現了Runnable介面的一個實例,代表一個線程的實例。啟動線程的唯一方法就是通過Thread類的start()實例方法。start()方法是一個native方法,它將啟動一個新線程,並執行run()方法。這種方式實現多線程很簡單,通過自己的類直接extend Thread,並覆寫run()方法,就可以啟動新線程並執行自己定義的run()方法。例如:

public class StartThread extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 120; i++) {
            System.out.println("聽歌。。。");
        }
    }

    public static void main(String[] args) {
        StartThread st = new StartThread();
        st.start();
        for (int i = 0; i < 120; i++) {
            System.out.println("敲代碼。。。");
        }
    }
}
  • 實現Runnable介面

如果自己的類已經extends另一個類,就無法直接extends Thread,此時,可以實現一個Runnable介面,如下:

public class StartRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 120; i++) {
            System.out.println("聽歌。。。");
        }
    }

    public static void main(String[] args) {
        StartRunnable sr = new StartRunnable();
        Thread t = new Thread(sr);
        t.start();
        for (int i = 0; i < 120; i++) {
            System.out.println("敲代碼。。。");
        }
    }
}
  • 實現Callable介面

實現Callable介面實現線程開啟相比較上兩個步驟會多一些,是juc併發包下的一個介面。

public class StartCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        for (int i = 0; i < 120; i++) {
            System.out.println("聽歌。。。");
        }
        return 1;
    }
    public static void main(String[] args) throws Exception {
        StartCallable sc = new StartCallable();
        ExecutorService es = Executors.newFixedThreadPool(1);
        Future<Integer> submit = es.submit(sc);
        Integer result = submit.get();
        es.shutdownNow();
        System.out.println(result);
        for (int i = 0; i < 120; i++) {
            System.out.println("敲代碼。。。");
        }
    }
}

線程狀態

線程在生命周期內,共五大狀態
在這裡插入圖片描述
具體說明:

  • 實例線程--->新生狀態
  • 調用start()--->就緒狀態
  • 操作系統CPU調度器分配好進行調用--->運行狀態
  • 線程正常執行完畢或外部干涉--->死亡狀態
  • IO文件讀寫、調用sleep()、調用wait()--->阻塞狀態
    在這裡插入圖片描述
    線程主要方法:
    在這裡插入圖片描述

    線程終止

停止線程的stop()方法jdk已經過失,不推薦使用,如何優雅的停止線程呢?舉例:

public class Study implements Runnable {
    // 線程類中,定義線程體使用的標識
    private boolean stop = true;

    private String name;

    Study(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        int i = 0;
        // 線程體使用該標識
        while (stop) {
            System.out.println(name + "線程正常運行。。。===>" + i++);
        }
    }
    // 對外提供方法改變標識
    public void stop() {
        this.stop = false;
    }

    public static void main(String[] args) {
        Study s = new Study("張三");
        Thread thread = new Thread(s);
        thread.start();
        for (int i = 0; i < 1000; i++) {
            if (i == 900) {
                s.stop();
                System.out.println("game over。。。。。。");
            }
            System.out.println("main====>" + i);
        }
    }
}

暫停 sleep()

  • sleep(時間)指定當前線程阻塞的毫秒數
  • sleep()存在異常 InterruptedExcetion
  • sleep()時間到達後線程進入就緒狀態
  • sleep()可以模擬網路延時、倒計時等
  • 每一個對象都有一個鎖,sleep()不會釋放鎖
  • 舉例:
// sleep() 模擬倒計時
public static void main(String[] args) throws InterruptedException {
    // 當前時間戳
    long millis = System.currentTimeMillis();
    // 當前時間加十秒
    Date endTime = new Date(millis + 1000 * 10);
    long end = endTime.getTime();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    for (; ; ) {
        System.out.println(sdf.format(endTime));
        // 睡眠一秒
        Thread.sleep(1000L);
        // 獲取倒計時一秒後的時間
        endTime = new Date(endTime.getTime() - 1000L);
        if ((end - 10000) >= endTime.getTime()) {
            break;
        }
    }
}

yield()

  • 禮讓線程,讓當前正在執行的線程暫停
  • 不是阻塞線程,而是將線程從 運行狀態 轉入就緒狀態
  • 讓CPU調度器重新調度

舉例:

public class Yield {
    public static void main(String[] args) {
        new Thread(()-> {
            for (int i = 0; i < 100; i++) {
                System.out.println("我是另一個線程。。。");
            }
        }).start();
        for (int i = 0; i < 100; i++) {
            if (i % 20 == 0) {
                System.out.println(i + "-->開始禮讓");
                // main線程進行禮讓
                Thread.yield();
            }
            System.out.println("main 線程-->" + i);
        }
    }
}

註意:yield是讓線程進入就緒狀態,不是阻塞狀態。

插隊 join()

join() 合併線程,待此線程執行完畢後,再執行其他線程,其他線程阻塞

舉例:

public class Example {
    public static void main(String[] args) {
        System.out.println("買煙的故事。。。");
        new Thread(new Father()).start();
    }
}

class Father extends Thread {
    @Override
    public void run() {
        System.out.println("老爸抽煙,煙沒了,讓兒子買煙。");
        Thread t = new Thread(new Son());
        t.start();
        try {
            // Father 線程被阻塞
            t.join();
            System.out.println("兒子把煙買來了,交給老爸,老爸把剩餘零錢給了兒子。");
        } catch (InterruptedException e) {
            System.out.println("兒子走丟了。。。");
        }
    }
}

class Son extends Thread {
    @Override
    public void run() {
        System.out.println("接過老爸的錢去買煙");
        System.out.println("路邊有個游戲廳,玩了10秒");
        for (int i = 0; i < 10; i++) {
            System.out.println(i + "秒過去了。。。");
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("突然想起還要買煙,趕緊買煙。");
        System.out.println("手拿一包中華,回家了。");
    }
}

深度觀察狀態

/**
 * 觀察線程狀態
 */
public class AllState {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()-> {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(100L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("....");
            }
        });
        // 觀察狀態
        Thread.State state = t.getState();
        // 新創建處理新生狀態,state為NEW
        System.out.println(state.toString());
        t.start();
        state = t.getState();
        // 線程就緒,就緒狀態到運行狀態是CPU控制的,所以一般調用了線程的 start()方法後,
        // state為RUNNABLE
        System.out.println(state.toString());

        for (; state != Thread.State.TERMINATED;) {
            Thread.sleep(200L);
            // 線程阻塞 state為TIMED_WAITING
            state = t.getState();
            System.out.println(state.toString());
        }
        // 線程運行結束 state為TERMINATED
        state = t.getState();
        System.out.println(state.toString());
    }
}

線程優先順序

在多個線程同時執行的時候,線程調度器會根據優先順序,優先調用級別高的線程。(只是決定優先調用,不代表先後順序。)

  • MAX_PRIORITY(10,線程可以擁有的最大優先順序)
  • MIN_PRIORITY(1,線程可以擁有的最小優先順序)
  • NORM_PRIORITY(5,分配給線程的預設優先順序)

舉例:

public class PriorityTest {
    public static void main(String[] args) {
        TestPriority tp = new TestPriority();
        Thread t = new Thread(tp);
        t.setPriority(Thread.MAX_PRIORITY);
        t.start();
        Thread t1 = new Thread(tp);
        t1.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        Thread t2 = new Thread(tp);
        t2.setPriority(Thread.MAX_PRIORITY);
        t2.start();
        Thread t3 = new Thread(tp);
        t3.setPriority(Thread.MIN_PRIORITY);
        t3.start();
        Thread t4 = new Thread(tp);
        t4.setPriority(Thread.MIN_PRIORITY);
        t4.start();
        Thread t5 = new Thread(tp);
        t5.setPriority(Thread.MIN_PRIORITY);
        t5.start();
    }
}

class TestPriority implements Runnable {
    @Override
    public void run() {
        Thread thread = Thread.currentThread();
        String threadName = thread.getName();
        int threadPriority = thread.getPriority();
        System.out.println(threadName + "--->" + threadPriority);
        Thread.yield();
    }
}

如果優先順序高的話,那麼先執行的概率會大一些,但不是絕對。

守護線程

守護線程是為用戶線程服務的,JVM停止不用等待守護線程執行完畢。

  • 線程分為用戶線程和守護線程
  • 虛擬機必須確保用戶線程執行完畢
  • 虛擬機不用等待守護線程執行完畢
  • 如後臺記錄操作日誌、監控記憶體使用等

預設情況下,線程都是用戶線程,虛擬機等待所有用戶線程執行完畢才會停止。

public class DaemonTest {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();
        Thread t = new Thread(god);
        // 將用戶線程調整為守護線程(預設為false)
        t.setDaemon(true);
        t.start();
        new Thread(you).start();
        // God調整為守護線程時,當You線程結束後,God也隨即結束。
    }
}
// 我
class You implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            System.out.println("幸福的生活著。。。");
        }
        System.out.println("Game Over!");
    }
}
// 上帝
class God implements Runnable {
    @Override
    public void run() {
        for (; ;) {
            System.out.println("上帝守護你。。。");
        }
    }
}

其它常用方法

  • isAlive(),判斷當前線程是否活著,即線程是否還未終止
  • setName(),給當前線程賦名字
  • getName(),獲取當前線程名字
  • currentThread(),取得當前運行的線程對象,或者說是獲取自己本身

舉例 :

public class OtherMethod {
    public static void main(String[] args) throws Exception {
        Thread thread = Thread.currentThread();
        System.out.println(thread);
        System.out.println(thread.isAlive());
        System.out.println(thread.getName());

        MyInfo myInfo = new MyInfo("張三");
        Thread t = new Thread(myInfo);
        t.setName("李四");
        t.start();
        Thread.sleep(2000L);
        System.out.println(t.isAlive());
    }
}

class MyInfo implements Runnable {
    private String name;
    MyInfo(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "--->" + name);
    }
}

線程同步

講到線程同步就要理解什麼是併發了,併發就是同一個對象同時被多個線程訪問。一旦出現併發就會線程不安全最終就可能導致數據結果不准確等一些問題。

線程同步本質就是一種等待機制,多個需要同時訪問此對象的線程進入這個對象的 等待池形成隊列 ,等待前面的線程使用完畢後,先一個線程再使用。

如何保證線程安全:

隊列形式,排隊執行,通過鎖的方式,標誌某個線程正在占用某個資源,處理完畢後釋放鎖讓隊列中其它線程繼續執行。
在這裡插入圖片描述

synchronized

synchronized 關鍵字包括兩種用法,synchronized 方法 和 synchronized 代碼塊

synchronized 方法控制對“成員變數|類變數”對象的訪問,每個對象對應一把鎖,每個 synchronized 方法都必須獲得調用該方法的對象的鎖才能執行,否則所屬線程阻塞,方法一旦執行,就獨占該鎖,直到從該方法返回時才將鎖釋放,此後被阻塞的線程方能獲得該鎖,重新進入可執行狀態。

synchronized方法
/**
 * 模擬搶票
 * 保證線程安全,併發時保證數據的正確性、效率儘可能高
 */
public class SyncWeb12306 implements Runnable {
    // 票數
    private int ticketNums = 10;
    private boolean flag = true;
    @Override
    public void run() {
        for (; ; ) {
            if (!flag) {
                break;
            }
            test();
        }
    }
    // 使用 synchronized 關鍵字 鎖住資源(this)
    public synchronized void test() {
        if (ticketNums <= 0) {
            this.flag = false;
            return;
        }
        try {
            Thread.sleep(200L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--);
    }
    public static void main(String[] args) {
        // 一份資源
        SyncWeb12306 web = new SyncWeb12306();
        // 多個線程同時訪問
        new Thread(web, "張三").start();
        new Thread(web, "李四").start();
        new Thread(web, "王五").start();
    }
}
synchronized代碼塊
/**
 * 模擬併發操作取錢
 */
public class SyncDrawMoney {
    int money;  // 金額
    String name;    // 名稱
    SyncDrawMoney(int money, String name) {
        this.money = money;
        this.name = name;
    }
    public static void main(String[] args) {
        SyncDrawMoney drawMoney = new SyncDrawMoney(100, "買車");
        SyncDrawing you = new SyncDrawing(drawMoney, 100, "張三");
        SyncDrawing wife = new SyncDrawing(drawMoney, 100, "張三老婆");
        you.start();
        wife.start();
    }
}

// 模擬取款
class SyncDrawing extends Thread {
    SyncDrawMoney drawMoney;    // 取錢賬戶
    int drawingMoney;   // 取的錢數
    int packetTotal;   // 口袋裡的錢
    public SyncDrawing(SyncDrawMoney drawMoney, int drawingMoney, String name) {
        super(name);
        this.drawMoney = drawMoney;
        this.drawingMoney = drawingMoney;
    }

    @Override
    public void run() {
        test();
    }

    private void test() {
        if (drawMoney.money > 0) {
            // 鎖住具體要操作的對象
            synchronized (drawMoney) {
                if (drawMoney.money < drawingMoney) {
                    System.out.println(Thread.currentThread().getName() + "取" + drawingMoney + "但是,餘額還有" + drawMoney.money);
                    System.out.println("錢不夠了。。。");
                    return;
                }
                if (drawMoney.money - drawingMoney < 0) {
                    System.out.println(Thread.currentThread().getName() + "取" + drawingMoney + "但是,餘額還有" + drawMoney.money);
                    System.out.println("沒錢了。。。");
                    return;
                }
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                drawMoney.money -= drawingMoney;
                packetTotal += drawingMoney;
                System.out.println(Thread.currentThread().getName() + "--->賬戶餘額為:" + drawMoney.money);
                System.out.println(Thread.currentThread().getName() + "--->口袋錢為:" + packetTotal);
            }
        } else {
            System.out.println("賬戶沒錢了。。。");
        }
    }
}

性能分析

public class PropertyWeb12306 implements Runnable {

    // 票數
    private Integer ticketNums = 10;

    private boolean flag = true;

    @Override
    public void run() {
        for (; ; ) {
            if (!flag) {
                break;
            }
            test3();
        }
    }

    // 儘可能鎖定合理範圍(合理範圍不是指代碼,指的是數據的完整性)
    void test3() {
        if (ticketNums <= 0) {
            this.flag = false;
            return;
        }
        synchronized (this) {
            if (ticketNums <= 0) {
                this.flag = false;
                return;
            }
            try {
                Thread.sleep(200L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--);
        }
    }

    // 同步代碼塊範圍小,鎖不住,導致線程不安全
    void test2() {
        synchronized (this) {
            if (ticketNums <= 0) {
                this.flag = false;
                return;
            }
        }
        try {
            Thread.sleep(200L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--);
    }

    // 同步代碼塊,鎖住當前對象,範圍大,雖然實現了線程安全,但是性能低下。
    void test1() {
        synchronized (this) {
            if (ticketNums <= 0) {
                this.flag = false;
                return;
            }
            try {
                Thread.sleep(200L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--);
        }
    }

    // 線程安全,同步
    synchronized void test() {
        if (ticketNums <= 0) {
            this.flag = false;
            return;
        }
        try {
            Thread.sleep(200L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--);
    }

    public static void main(String[] args) {
        // 一份資源
        PropertyWeb12306 web = new PropertyWeb12306();
        // 多個線程同時訪問
        new Thread(web, "張三").start();
        new Thread(web, "李四").start();
        new Thread(web, "王五").start();
    }
}

註意:實際開發中,儘量多用同步代碼塊,少用同步方法

線程同步小案例

購買電影票小案例

/**
 * 快樂影院
 */
public class HappyCinema {
    public static void main(String[] args) {
        // 可用位置
        List<Integer> available = new ArrayList<>();
        available.add(1);
        available.add(2);
        available.add(4);
        available.add(6);
        available.add(5);
        available.add(7);
        available.add(3);
        // 影院
        Cinema c = new Cinema(available, "夜上海影院");
        // 來兩個顧客
        List<Integer> seats01 = new ArrayList<>();
        seats01.add(1);
        seats01.add(2);
        seats01.add(4);
        new Thread(new Customer(c, seats01), "張三").start();

        List<Integer> seats02 = new ArrayList<>();
        seats02.add(4);
        seats02.add(6);
        seats02.add(7);
        new Thread(new Customer(c, seats02), "李四").start();
    }
}

/**
 * 顧客
 */
class Customer implements Runnable {
    // 電影院
    Cinema cinema;
    // 需要幾個位置
    List<Integer> seats;
    Customer(Cinema cinema, List<Integer> seats) {
        this.cinema = cinema;
        this.seats = seats;
    }
    @Override
    public void run() {
        Thread thread = Thread.currentThread();
        synchronized (cinema) {
            boolean flag = cinema.bookTickets(this.seats);
            if (flag) {
                System.out.println(thread.getName() + "-->購票成功。。。" + "位置為:" + this.seats);
            } else {
                System.out.println(thread.getName() + "-->購票失敗***" + "位置不夠");
            }
        }
    }
}

/**
 * 影院
 */
class Cinema {
    List<Integer> available;  // 可用位置
    String name;    // 名稱
    Cinema(List<Integer> available, String name) {
        this.available = available;
        this.name = name;
    }
    // 購票
    boolean bookTickets(List<Integer> seats) {
        System.out.println("歡迎光臨【" + this.name + "】當前可用的位置為" + available);
        System.out.println(Thread.currentThread().getName() + "-->想要購買" + seats);
        List<Integer> copy = new ArrayList<>(available);
        // 相減
        copy.removeAll(seats);
        // 判斷大小
        if (seats.size() != (available.size() - copy.size())) {
            return false;
        }
        available = copy;
        return true;
    }
}

購買火車票小案例

/**
 * 購買火車票  線程安全版
 */
public class Happy12306 {
    public static void main(String[] args) {
        Web12306 web = new Web12306(4, "杭州");
        new Passenger(web, "張三", 2).start();
        new Passenger(web, "李四", 1).start();
    }
}

// 乘客
class Passenger extends Thread {
    int seats;
    public Passenger(Runnable target, String name, int seats) {
        super(target, name);
        this.seats = seats;
    }
}

// 火車票網站
class Web12306 implements Runnable {
    int available;  // 可用位置
    String name;    // 名稱

    public Web12306(int available, String name) {
        this.available = available;
        this.name = name;
    }

    @Override
    public void run() {
        Passenger p = (Passenger) Thread.currentThread();
        boolean flag = this.bookTickets(p.seats);
        if (flag) {
            System.out.println("出票成功" + p.getName() + "位置為:" + p.seats);
        } else {
            System.out.println("出票失敗" + p.getName() + "位置不夠");
        }
    }

    // 購票
    synchronized boolean bookTickets(int seats) {
        System.out.println("可用位置為:" + this.available);
        if (seats > this.available) {
            return false;
        }
        available -= seats;
        return true;
    }
}

死鎖

多個線程各自占有一些共用資源,並且互相等待其他線程占有的資源才能進行,而導致兩個或者多個線程都在等待對方釋放資源,都停止執行的情形。某一個同步塊同時擁有“兩個以上對象的鎖”時,就可能會發生“死鎖”的問題。

通俗一些就是:你給錢,我給貨。我想讓你先給貨,你想讓我先給錢,一直僵持著。

過多的同步有可能會造成死鎖

如何避免:

不要再同步一個代碼塊同時持有多個對象的鎖

併發協作

生產者消費者模式

本質是一個線程同步問題,生產者和消費者共用同一個資源,並且生產者和消費者之間相互依賴,互為條件。

  • 對於生產者而言,沒有生產產品之前,要通知消費者等待。而生產了之後,需要馬上通知消費者
  • 對於消費者而言,在消費之後,要通知生產者已經消費結束,需要繼續生產新產品以供消費
  • 在生產者消費者問題中,僅有 synchronized 時不夠的,還需要等待和通知等操作
    • synchronized 可阻止併發更新同一個共用資源,實現同步
    • synchronized 不能用來實現不同線程之間的消息傳遞(通信)
      在這裡插入圖片描述
      Java提供了3個方法解決線程之間的通信問題
方法名 作用
final void wait() 表示線程一直等待,直到其他線程通知,與sleep()不同,會釋放鎖
final void wait(long timeout) 指定等待的毫秒數
final void notifiy() 喚醒一個處於等待狀態的線程
final void notifiyAll() 喚醒同一個對象上所有調用wait()方法的線程,優先順序別高的線程優先調度

註意:以上方法都只能在同步方法或者同步代碼塊中使用,否則會拋出異常

實現方式一:管程法
在這裡插入圖片描述

/**
 * 協作模型:
 * 生產者消費者模式方式一:管程法
 */
public class CoTest01 {
    public static void main(String[] args) {
        SyncContainer container = new SyncContainer();
        // 生產者
        new Productor(container).start();
        // 消費者
        new Consumer(container).start();
    }
}
// 生產者
class Productor extends Thread {
    SyncContainer container;

    public Productor(SyncContainer container) {
        this.container = container;
    }

    @Override
    public void run() {
        // 開始生產
        for (int i = 0; i < 100; i++) {
            System.out.println("生產第" + i + "個饅頭。。。");
            container.push(new SteamedBun(i));
        }
    }
}

// 消費者
class Consumer extends Thread {
    SyncContainer container;

    public Consumer(SyncContainer container) {
        this.container = container;
    }
    @Override
    public void run() {
        // 開始消費
        for (int i = 0; i < 100; i++) {
            System.out.println("消費第" + container.pop().id + "個饅頭。。。");
        }
    }
}

// 緩衝區
class SyncContainer {
    SteamedBun[] steamedBuns = new SteamedBun[10];  // 存儲容器
    int count = 0;  // 計數器

    // 存儲  生產
    synchronized void push(SteamedBun steamedBun) {
        // 不能生產
        if (count == steamedBuns.length) {
            try {
                this.wait();    // 線程阻塞 消費者通知生產解除
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        steamedBuns[count] =steamedBun;
        count++;
        // 存在數據了,可以通知消費
        this.notifyAll();
    }

    // 獲取 消費
    synchronized SteamedBun pop() {
        if (count == 0) {
            try {
                this.wait();    // 線程阻塞 生產者通知消費,解除阻塞
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count--;
        this.notifyAll();   // 存在空間 喚醒所有
        return steamedBuns[count];
    }
}

// 數據,舉例為饅頭
class SteamedBun {
    int id;

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

實現方式二:信號燈法
在這裡插入圖片描述

/**
 * 協作模型:
 * 生產者消費者模式方式一:信號燈法
 * 藉助標註位
 */
public class CoTest02 {
    public static void main(String[] args) {
        Tv tv = new Tv();
        new Player(tv).start();
        new Watcher(tv).start();
    }
}
// 生產者 演員
class Player extends Thread {
    Tv tv;

    public Player(Tv tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if ((i % 2) == 0) {
                this.tv.play("奇葩說。。。");
            } else {
                this.tv.play("直播廣告。。。");
            }
        }
    }
}
// 消費者觀眾
class Watcher extends Thread {
    Tv tv;

    public Watcher(Tv tv) {
        this.tv = tv;
    }

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

// 同一個資源 電視
class Tv {
    String voice;
    boolean flag = true; // 信號燈,如果為 true 演員表演觀眾觀看,否則觀眾觀看演員等待

    // 表演
    synchronized void play(String voice) {
        // 演員等待
        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.voice = voice;
        // 表演
        System.out.println("表演了:" + this.voice);
        this.notifyAll();   // 喚醒
        this.flag = !this.flag;
    }
    // 觀看
    synchronized void watch() {
        // 觀眾等待
        if (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 觀看
        System.out.println("聽到了:" + this.voice);
        this.notifyAll();   // 喚醒
        this.flag = !this.flag;
    }
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文將介紹如何使用Docker Compose搭建Istio。Istio號稱支持多種平臺(不僅僅Kubernetes)。然而,官網上非基於Kubernetes的教程仿佛不是親兒子,寫得非常隨便,不僅缺了一些內容,而且還有坑。本文希望能補實這些內容。我認為在學習Istio的過程中,相比於Kuberne ...
  • 工廠方法模式概述 工廠方法模式是為了彌補簡單工廠模式的不足並且繼承它的優點而延生出的一種設計模式,屬於GoF中的一種。它能更好的符合開閉原則的要求。 舉個例子:大眾汽車公司想必大家都不陌生,它旗下也有不少汽車品牌。大眾汽車公司就好比一個汽車工廠,負責生產和銷售汽車。它可以為客戶提供一個客戶需要的汽車 ...
  • 1.上傳視頻信息的jsp頁面uploadVideo.jsp <body background="image/bk_hero.jpg"><div id="upld" style="height:300px;width:300px;margin-left: 300px;margin-top: 100px ...
  • 後臺servlet設置 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String method=reque ...
  • 清明節和朋友去被抖音帶火的一個餐廳,下午兩點鐘取晚上的號,前面已經有十幾桌了,四點半餐廳開始正式營業,等輪到我們已經近八點了。餐廳分為幾個區域,只有最火的區域(在小船上)需要排號,其他區域基本上是隨到隨吃的,最冷清的區域幾乎都沒什麼人。菜的價格異常的貴,味道也並不好。最後送出兩張圖: 好了,進入今天 ...
  • 簡介 當你的程式不能正常運行的時候,Python會在控制台列印一段提醒,告訴你一個錯誤,這個錯誤就是異常。 錯誤 我在控制台寫了一段無效的代碼,將print()的括弧去掉,在執行這條語句的時候,系統提示語法錯誤,無效的語句(我百度翻譯的)。這就是錯誤處理器所作的工作。 再換一種方式寫錯誤: 在程式獲 ...
  • 1.防止用戶沒有登錄即可訪問其他頁面 1.1>從session中判斷用戶是否登陸 ServletActionContext.getRequest().getSession();以此為依據是否放行 2.註冊自定義攔截器 3.設置預設攔截器 ...
  • 1>***貓: python基礎類: 技能類: 業務類: 你對電商的業務瞭解嗎? 我問你一個具體的電商的業務啊:電商在大多數領域中都是有庫存的,我在下訂單的時候,我的庫存的變化,你知道嗎?(我問的是你怎麼去處理庫存的變化?)因為客戶下單的時候,有些是去減庫存了,有些不減對吧?你的理解呢? 你事務的時 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...