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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...