Java多線程筆記

基本概念

程序(program):是為完成特定任務、用某種語言編寫的一組指令的集合。即指一段靜態(tài)的代碼,靜態(tài)對象。

進程(process):是程序的一次執(zhí)行過程,或是正在運行的一個程序。是一個動態(tài)的過程:有它自身的產生、存在和消亡的過程。--生命周期

線程(thread):進程可進一步細化為線程,是一個程序內部的一條執(zhí)行路徑。

  • 若一個進程同一時間 并行執(zhí)行多個線程,就是支持多線程的。
  • 線程作為調度和執(zhí)行的單位,每個線程擁有獨立的運行棧和程序計數器(pc),線程切換的開銷小。
  • 一個進程中的多個線程共享相同的內存單元/內存地址空間?它們從同一堆中分配對象,可以訪問相同的變量和對象。這就使得線程間通信更簡便、高效。但多個線程操作共享的系統(tǒng)資源可能就會帶來安全的隱患。

何時需要多線程

  • 程序需要同時執(zhí)行兩個或多個任務。
  • 程序需要實現一些需要等待的任務時,如用戶輸入、文件讀寫操作、網絡操作、搜索等。
  • 需要一些后臺運行的程序時。

線程的創(chuàng)建和使用

方式一:繼承Thread類

1)定義子類繼承Thread類。
2)子類中重寫Thread類中的run方法。
3)創(chuàng)建Thread子類對象,即創(chuàng)建了線程對象。
4)調用線程對象start方法:啟動線程,調用run方法。

注意點:

  1. 如果自己手動調用run()方法,那么就只是普通方法,沒有啟動多線程模式。
  2. run()方法由JVM調用,什么時候調用,執(zhí)行的過程控制都有操作系統(tǒng)的CPU調度決定。
  3. 想要啟動多線程,必須調用start方法。
  4. 一個線程對象只能調用一次start()方法啟動,如果重復調用了,則將拋出非法線程狀態(tài)的異常"IllegalThreadStateException"。

代碼:

public class ThreadTest {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
        MyThread thread2 = new MyThread();
        thread2.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println(i+"---- main ----");
        }
    }
}


class MyThread extends Thread{

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

方式二:實現Runnable接口

  1. 創(chuàng)建一個實現Runnable接口的類。
  2. 在實現類中實現Runnable接口中的run()抽象方法。
  3. 創(chuàng)建實現類的對象。
  4. 將此對象作為參數傳遞到Thread類的構造器中,創(chuàng)建Thread類的對象。
  5. 通過Thread類的對象調用start()

示例:

public class ThreadTest2 {
    public static void main(String[] args) {
        MyThread thread = new MyThread();

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

class MyThread implements Runnable {

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

繼承Thread方式和實現Runnable方式的聯系與區(qū)別

區(qū)別

  • 繼承Thread: 線程代碼存放Thread子類run方法中。

  • 實現Runnable:線程代碼存在接口的子類的run方法。

實現Runnable方式的好處(優(yōu)先選擇)

  • 避免了單繼承的局限性。
  • 多個線程可以共享同一個接口實現類的對象,非常適合多個相同線程來處理同一份資源。

方式三:實現Callable接口(JDK5.0新增)

與使用Runnable相比,Callable功能更強大些

  • 相比run()方法,可以有返回值
  • 方法可以拋出異常
  • 支持泛型的返回值
  • 需要借助FutureTask類,比如獲取返回結果

Future接口

  • 可以對具體Runnable、Callable任務的執(zhí)行結果進行取消、查詢是否完成、獲取結果等。
  • FutrueTask是Futrue接口的唯一的實現類
  • FutureTask同時實現了Runnable, Future接口。它既可以作為Runnable被線程執(zhí)行,又可以作為Future得到Callable的返回值。

使用步驟

  1. 創(chuàng)建Callable接口的實現類

  2. 實現call方法,將此線程需要執(zhí)行的操作聲明在call()中

  3. 創(chuàng)建Callable接口實現類的對象

  4. 將此Callable接口實現類的對象傳遞給FutureTask的構造器中,創(chuàng)建FutureTask的對象

  5. 將FutureTask的對象作為參數傳遞到Thread類的構造器中,創(chuàng)建Thread對象,并調用start()啟動線程

  6. 獲取Callable中call()的返回值,該操作需要在啟動線程后進行,否則將卡在此處。

例:利用線程獲取0~100中偶數的返回值


import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/*
 * 實現Callable接口創(chuàng)建線程
 */

//1. 創(chuàng)建Callable接口的實現類
class NumberCallable implements Callable {

    // 2. 實現call方法,將此線程需要執(zhí)行的操作聲明在call()中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
                sum += i;
            }
        }
        return sum; // 如果沒有返回值,就return null;
    }
}

public class CallableThreadTest {
    public static void main(String[] args) {
        // 3. 創(chuàng)建Callable接口實現類的對象
        NumberCallable number = new NumberCallable();
        // 4. 將此Callable接口實現類的對象傳遞給FutureTask的構造器中,創(chuàng)建FutureTask的對象
        FutureTask task = new FutureTask(number);
        // 5. 將FutureTask的對象作為參數傳遞到Thread類的構造器中,創(chuàng)建Thread對象,并調用start()啟動線程
        new Thread(task).start();
        
        try {
            // 6. 獲取Callable中call()的返回值,該操作需要在啟動線程后進行,否則將卡在此處。
            // get()返回值即為FutureTask構造參數Callable實現類重寫的call()的返回值。
            Object sum = task.get();
            System.out.println("sum:" + sum);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ExecutionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
    }
}

方式四:使用線程池(JDK5.0新增)

背景:經常創(chuàng)建和銷毀、使用量特別大的資源,比如并發(fā)情況下的線程,對性能影響很大。

思路:提前創(chuàng)建好多個線程,放入線程池中,使用時直接獲取,使用完放回池中??梢员苊忸l繁創(chuàng)建銷毀、實現重復利用。類似生活中的公共交通工具。

好處

  • 提高響應速度(減少了創(chuàng)建新線程的時間)
  • 降低資源消耗(重復利用線程池中線程,不需要每次都創(chuàng)建)
  • 便于線程管理
    • corePoolSize:核心池的大小
    • maximumPoolSize:最大線程數
    • keepAliveTime:線程沒有任務時最多保持多長時間后會終止

線程池相關API

  • JDK 5.0起提供了線程池相關API:ExecutorService 和 Executors

  • ExecutorService:真正的線程池接口。常見子類ThreadPoolExecutor

    • void execute(Runnable command) :執(zhí)行任務/命令,沒有返回值,一般用來執(zhí)行Runnable
    • <T> Future<T> submit(Callable<T> task):執(zhí)行任務,有返回值,一般又來執(zhí)行Callable
    • void shutdown() :關閉連接池
  • Executors:工具類、線程池的工廠類,用于創(chuàng)建并返回不同類型的線程池

    • Executors.newCachedThreadPool():創(chuàng)建一個可根據需要創(chuàng)建新線程的線程池
    • Executors.newFixedThreadPool(n); 創(chuàng)建一個可重用固定線程數的線程池
    • Executors.newSingleThreadExecutor() :創(chuàng)建一個只有一個線程的線程池
    • Executors.newScheduledThreadPool(n):創(chuàng)建一個線程池,它可安排在給定延遲后運行命令或者定期地執(zhí)行。

使用ThreadPoolExecutor設置線程池參數

import java.util.concurrent.ThreadPoolExecutor;

ExecutorService executorService = Executors.newFixedThreadPool(10);
ThreadPoolExecutor executor = (ThreadPoolExecutor)executorService;

executor.setCorePoolSize(15);
executor.setKeepAliveTime(3600, TimeUnit.SECONDS);

例:

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/*
 * 使用線程池
 */

class NumberRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            if (i % 2 != 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

public class ThreadPoolTest {
    public static void main(String[] args) {
        // 1. 提供指定線程數量的線程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        // 2. 執(zhí)行指定的線程操作。需要提供實現Runnable接口或Callable接口的實現類的對象

        // 2.1 使用execute執(zhí)行Runnable接口的實現類對象
        executorService.execute(new NumberRunnable());
        // 2.2 使用submit執(zhí)行Callable接口的實現類對象
        Future future = executorService.submit(new NumberCallable());
        try {
            System.out.println(Thread.currentThread().getName() + ":Total:" + future.get());
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ExecutionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        // 關閉連接池
        executorService.shutdown();
    }
}

Thread中常用方法

1.void start()

啟動線程,并執(zhí)行對象的run()方法

2.run()

線程在被調度時執(zhí)行的操作,通常需要重寫Thread類中的此方法,將創(chuàng)建的線程要執(zhí)行的操作聲明在此方法中

3.static currentThread()

返回當前線程。在Thread子類中就是this,通常用于主線程和Runnable實現類

4.getName()/setName()

返回/設置線程的名稱

5.static void yield()

線程讓步,釋放當前CPU的執(zhí)行權

  • 暫停當前正在執(zhí)行的線程,把執(zhí)行機會讓給優(yōu)先級相同或更高的線程
  • 若隊列中沒有同優(yōu)先級的線程,忽略此方法

6.join()

當某個程序執(zhí)行流中調用其他線程的join()方法時,調用線程將被阻塞,直到join()方法加入的join線程執(zhí)行完為止。

也就是說,在線程a中調用線程b的join(),此時線程a就進入阻塞狀態(tài),直到線程b完全執(zhí)行完畢以后,線程a才結束阻塞狀態(tài)。

  • 低優(yōu)先級的線程也可以獲得執(zhí)行

7.stop() "Deprecated"

強制線程生命期結束,不推薦使用

8.static void sleep(long millis) :(指定時間:毫秒)

令當前活動線程在指定時間段內放棄對CPU控制,使其他線程有機會被執(zhí)行,時間到后重排隊。

就是讓當前線程“睡眠”指定的millis毫秒,在指定的millis毫秒時間內,當前線程是阻塞狀態(tài)。

  • 拋出InterruptedException異常

9.boolean isAlive()

返回boolean,判斷線程是否還活著

線程的優(yōu)先級

線程的優(yōu)先級等級

  • MAX_PRIORITY :10
  • MIN _PRIORITY :1
  • NORM_PRIORITY :5

涉及的方法

  • getPriority():返回線程優(yōu)先值
  • setPriority(int newPriority):改變線程的優(yōu)先級

說明

  • 線程創(chuàng)建時繼承父線程的優(yōu)先級
  • 低優(yōu)先級只是獲得調度的概率低,并非一定是在高優(yōu)先級線程之后才被調用

線程的分類

Java中的線程分為兩類:一種是守護線程,一種是用戶線程

  • 它們在幾乎每個方面都是相同的,唯一的區(qū)別是判斷JVM何時離開。
  • 守護線程是用來服務用戶線程的,通過在start()方法前調用thread.setDaemon(true)可以把一個用戶線程變成一個守護線程。
  • Java垃圾回收就是一個典型的守護線程。
  • 若JVM中都是守護線程,當前JVM將退出。
  • 形象理解:兔死狗烹,鳥盡弓藏

線程的生命周期

JDK 中用Thread.State類定義了線程的幾種。

要想實現多線程,必須在主線程中創(chuàng)建新的線程對象。Java語言使用Thread類及其子類的對象來表示線程,在它的一個完整的生命周期中通常要經歷如下的 五種狀態(tài):

  • 新建:當一個Thread類或其子類的對象被聲明并創(chuàng)建時,新生的線程對象處于新建狀態(tài)
  • 就緒:處于新建狀態(tài)的線程被start()后,將進入線程隊列等待CPU時間片,此時它已具備了運行的條件,只是沒分配到CPU資源
  • 運行:當就緒的線程被調度并獲得CPU資源時,便進入運行狀態(tài),run()方法定義了線程的操作和功能
  • 阻塞:在某種特殊情況下,被人為掛起或執(zhí)行輸入輸出操作時,讓出CPU并臨時中止自己的執(zhí)行,進入阻塞狀態(tài)
  • 死亡:線程完成了它的全部工作或線程被提前強制性地中止或出現異常導致結束
線程狀態(tài)轉換圖

線程的同步

方式一:同步代碼塊

synchronized(同步監(jiān)視器){
//  需要被同步的代碼;
}

說明:

  1. 操作共享數據的代碼,即為需要被同步的代碼。不能包含代碼多了,也不能包含代碼少了。

  2. 共享數據:多個線程共同操作的變量。比如:ticket就是共享數據。

  3. 同步監(jiān)視器,俗稱:鎖。任何一個類的對象,都可以充當鎖。

    要求:多個線程必須要共用同一把鎖。

    在實現Runnable接口創(chuàng)建多線程的方式中,可以考慮使用this充當同步監(jiān)視器。

    在繼承Thread類創(chuàng)建多線程的方式中,慎用this充當同步監(jiān)視器,考慮使用當前類(類名.class)充當同步監(jiān)視器。

例:

  1. 在實現Runnable接口中使用同步代碼塊
public class SellTicketTest {
    public static void main(String[] args) {
        SellTickets tickets = new SellTickets();
        Thread window1 = new Thread(tickets);
        Thread window2 = new Thread(tickets);
        Thread window3 = new Thread(tickets);

        window1.setName("Window 1");
        window2.setName("Window 2");
        window3.setName("Window 3");

        window1.start();
        window2.start();
        window3.start();
    }
}

class SellTickets implements Runnable {
    private int tickets = 1000;
    // 同步鎖
    private Object lock = new Object();

    @Override
    public void run() {

        while (true) {
            synchronized (lock) {
                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + tickets);
                    tickets--;
                } else {
                    break;
                }
            }
        }

    }
}

  1. 在繼承Thread類中使用同步代碼塊
public class SellTicketTest2 {
    public static void main(String[] args) {
        Thread window1 = new SellTickets2();
        Thread window2 = new SellTickets2();
        Thread window3 = new SellTickets2();

        window1.setName("Window 1");
        window2.setName("Window 2");
        window3.setName("Window 3");

        window1.start();
        window2.start();
        window3.start();
    }
}

class SellTickets2 extends Thread {
    private int tickets = 1000;
  // 這里的lock是static的變量
    private static Object lock = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (lock) {
                /*
                 * 這行代碼如果放在while(true)上方,則會出現問題
                 * -- 直到while(true)執(zhí)行完畢,其他的線程才有可能進來,這時票已經賣完了
                 */
                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + tickets);
                    tickets--;
                } else {
                    break;
                }
            }
        }
    }
}

方式二:同步方法

如果操作共享數據的代碼證號完整的聲明在一個方法中,可以將此方法聲明為同步方法。

同步監(jiān)視器:

  • 非靜態(tài)的同步方法:this

  • 靜態(tài)的同步方法:類名.class

例:

非靜態(tài)的同步方法:

public class SellTicketTest3 {
    public static void main(String[] args) {
        SellTickets3 tickets = new SellTickets3();
        Thread window1 = new Thread(tickets);
        Thread window2 = new Thread(tickets);
        Thread window3 = new Thread(tickets);

        window1.setName("Window 1");
        window2.setName("Window 2");
        window3.setName("Window 3");

        window1.start();
        window2.start();
        window3.start();
    }
}

class SellTickets3 implements Runnable {
    private int tickets = 100;

    @Override
    public void run() {

        while (hasTicket()) {   
        }
    }
    
    private synchronized boolean hasTicket() {
        if (tickets > 0) {
            System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + tickets);
            tickets--;
            return true;
        } else {
            return false;
        }
    }
}

靜態(tài)的同步方法:

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

        Thread window1 = new SellTickets4();
        Thread window2 = new SellTickets4();
        Thread window3 = new SellTickets4();

        window1.setName("Window 1");
        window2.setName("Window 2");
        window3.setName("Window 3");

        window1.start();
        window2.start();
        window3.start();
    }
}

class SellTickets4 extends Thread {
    private static int tickets = 100;

    @Override
    public void run() {
        while (hasTicket()) {
        }
    }

    // 同步監(jiān)視器:SellTickets4.class
    private static synchronized boolean hasTicket() {
        if (tickets > 0) {
            System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + tickets);
            tickets--;
            return true;
        } else {
            return false;
        }
    }
}

方式三:Lock鎖??(JDK 5.0之后)

  • 從JDK 5.0開始,Java提供了更強大的線程同步機制——通過顯式定義同步鎖對象來實現同步。同步鎖使用Lock對象充當。
  • java.util.concurrent.locks.Lock接口是控制多個線程對共享資源進行訪問的工具。鎖提供了對共享資源的獨占訪問,每次只能有一個線程對Lock對象加鎖,線程開始訪問共享資源之前應先獲得Lock對象。
  • ReentrantLock 類實現了 Lock ,它擁有與 synchronized 相同的并發(fā)性和內存語義,在實現線程安全的控制,比較常用的是ReentrantLock,可以顯式加鎖、釋放鎖。

使用方法:

  1. 創(chuàng)建鎖:private static ReentrantLock lock = new ReentrantLock();
  2. 調用鎖定方法:lock()
  3. 調用解鎖方法:unlock()

使用時,使用try...finally...進行包裹,在try中進行上鎖,在finally中進行解鎖,以保證鎖一定會被釋放。

例:

import java.util.concurrent.locks.ReentrantLock;

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

        Thread window1 = new SellTickets5();
        Thread window2 = new SellTickets5();
        Thread window3 = new SellTickets5();

        window1.setName("Window 1");
        window2.setName("Window 2");
        window3.setName("Window 3");

        window1.start();
        window2.start();
        window3.start();
    }
}

class SellTickets5 extends Thread {
    private static int tickets = 100;
    // 1.創(chuàng)建lock
    private static ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                // 2.調用鎖定方法:lock()
                lock.lock();
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + tickets);
                    tickets--;
                } else {
                    break;
                }
            } finally {
                // 3.調用解鎖方法:unlock()
                lock.unlock();
            }
        }
    }
}

synchronized與Lock的異同

相同點:二者都可以解決線程安全問題

不同點:synchronized機制在執(zhí)行完相應的同步代碼后,自動釋放同步監(jiān)視器(鎖);Lock需要手動啟動同步(lock()),結束同步也需要手動實現(unlock())

優(yōu)先使用順序

Lock ? 同步代碼塊(已經進入了方法體,分配了相應資源)? 同步方法(在方法體之外)

單例設計式模式之懶漢式(線程安全)

class Singleton {
    private static Singleton instance = null;

    private Singleton() {
    }

    public static Singleton getInstance() {
    // 這一行提高了運行效率,在創(chuàng)建instance后,后續(xù)的線程可以直接獲取instance,而不需要再進入同步代碼塊進行查看,也就無需等待其他線程是否再執(zhí)行同步代碼塊
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

public class SingletonTest {
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2);
    }
}

死鎖的問題

死鎖:不同的線程分別占用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖。

說明:

  1. 出現死鎖后,不會出現異常,不會出現提示,只是所有的線程都處于阻塞狀態(tài),無法繼續(xù)
  2. 使用同步時,要避免出現死鎖

解決方法

  1. 專門的算法、原則
  2. 盡量減少同步資源的定義
  3. 盡量避免嵌套同步

線程的通信

相關的三個方法

wait():令當前線程掛起并放棄CPU、同步資源并等待,使別的線程可訪問并修改共享資源,而當前線程排隊等候其他線程調用notify()或notifyAll()方法喚醒,喚醒后等待重新獲得對監(jiān)視器的所有權后才能繼續(xù)執(zhí)行。

notify():喚醒正在排隊等待同步資源的線程中優(yōu)先級最高者結束等待

notifyAll():喚醒正在排隊等待資源的所有線程結束等待

說明:

  1. wait()、notify()、notifyAll()三個方法必須使用在synchronized代碼塊synchronized方法中,lock鎖的方式不適用。
  2. wait()、notify()、notifyAll()三個方法的調用者必須是synchronized代碼塊synchronized方法中的同步監(jiān)視器,否則會出現IllegalMonitorStateException異常。
  3. wait()、notify()、notifyAll()三個方法定義在java.lang.Object類中。因為這三個方法必須有鎖對象調用,而任意對象都可以作為synchronized的同步鎖,因此這三個方法只能在Object類中聲明。

基本使用

例:

使用兩個線程交替打印打印1-100。


public class ThreadCommunicationTest {
    public static void main(String[] args) {
        Number number = new Number();

        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);

        t1.setName("線程1");
        t2.setName("線程2");

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

class Number implements Runnable {
    private int number = 1;
    private Object lock = new Object();
    @Override
    public void run() {
        while (true) {
            synchronized (lock) {
                lock.notify();
                if (number <= 100) {
                    System.out.println(Thread.currentThread().getName() + ":" + number);
                    number++;
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
            }
        }
    }
}

sleep()和wait()的異同

相同點:一旦執(zhí)行方法,都可以使用當前的線程進入阻塞狀態(tài)。

不同點:

1)兩個方法聲明的位置不同:Thread類中聲明sleep(),Object類中聲明wait()

2) 調用的要求不同:sleep()可以在任何需要的場景下調用,wait()必須在同步代碼塊或同步方法中由同步監(jiān)視器調用

3) 關于是否釋放同步監(jiān)視器:如果兩個方法都使用在同步代碼塊或同步方法中,sleep()不會釋放鎖,wait()會釋放鎖。

經典例題:生產者/消費者問題

題目:生產者(Productor)將產品交給店員(Clerk),而消費者(Customer)從店員處取走產品,店員一次只能持有固定數量的產品(比如:20),如果生產者試圖生產更多的產品,店員會叫生產者停一下,如果店中有空位放產品了再通知生產者繼續(xù)生產;如果店中沒有產品了,店員會告訴消費者等一下,如果店中有產品了再通知消費者來取走產品。

這里可能出現兩個問題

  • 生產者比消費者快時,消費者會漏掉一些數據沒有取到。
  • 消費者比生產者快時,消費者會取相同的數據。
/*
 * 經典例題:生產者/消費者問題
 * 生產者(Producer)將產品交給店員(Clerk),而消費者(Customer)從店員處
 * 取走產品,店員一次只能持有固定數量的產品(比如:20),如果生產者試圖
 * 生產更多的產品,店員會叫生產者停一下,如果店中有空位放產品了再通
 * 知生產者繼續(xù)生產;如果店中沒有產品了,店員會告訴消費者等一下,如
 * 果店中有產品了再通知消費者來取走產品。
 */

public class ProducerConsumerTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Producer producer = new Producer(clerk);
        Consumer consumer1 = new Consumer(clerk);
        Consumer consumer2 = new Consumer(clerk);
        
        producer.setName("生產者1");
        consumer1.setName("消費者1");
        consumer2.setName("消費者2");
        
        producer.start();
        consumer1.start();
        consumer2.start();
    }
}

// 店員,相當于共享數據
class Clerk {
    private int productCount = 0;

    public synchronized void produceProduct() {

        if (productCount < 20) {
            // start produce
            productCount++;
            System.out.println(Thread.currentThread().getName() + ":生產第" + productCount + "個產品");
            notify();
        } else {
            // stop produce
            try {
                wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

    }

    public synchronized void consumeProduct() {
        if (productCount > 0) {
            // start consume
            System.out.println(Thread.currentThread().getName() + ":消費第" + productCount + "個產品");
            productCount--;
            notify();
        } else {
            // stop consume
            try {
                wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

}

// 生產者,線程1
class Producer extends Thread {
    private Clerk clerk;

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println("開始生產...");
        while (true) {
            try {
                Thread.sleep((int)(Math.random()*300));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.produceProduct();
        }
    }
}

// 消費者,線程2
class Consumer extends Thread {
    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println("消費產品...");
        while (true) {
            try {
                Thread.sleep((int)(Math.random()*1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.consumeProduct();
        }
    }
}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容