對于線程和線程池還有線程安全的理解

進程和線程

進程和線程都是一個時間段的描述,是CPU工作時間段的描述,不過是顆粒大小不同。

他們主要區(qū)別是:進程不共享內存,線程可以共享內存。

線程:

  • CPU中的Thread:
    CPU中的線程,我們也叫它們Thread,和OS中的線程的名字一樣。他們和cpu相關,常說的4核心8線程就是指cpu線程。CPU的Thread就那么固定幾個,是稀缺資源。
  • 操作系統(tǒng)中的Thread: 操作系統(tǒng)中的進程可以很多,進程中的線程就更多了。軟件操作系統(tǒng)調度的基本單位是OS的Thread。我們開發(fā)中所指的就是這個線程。

Thread和Runnable

Java中線程的創(chuàng)建有兩種方式: 1.通過繼承Thread類,重寫Thread的run()方法,將線程運行的邏輯放在其中。

2.通過實現(xiàn)Runnable接口,實例化Thread類。

我們通常使用第二種,因為可以復用Runnable,更容易實現(xiàn)資源共享,能多個線程同時處理一個資源。

// 1
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("this is a Runnable");
    }
}
// 2
public class MyThread extends Thread {
    @Override
    public void run() {
        super.run();
        System.out.println("this is thread");
    }
}

// 具體使用
public class Main {
    public static void main(String[] args) {
        // 第一種
        Thread thread1 = new Thread(new MyRunnable());
        thread1.start();
        // 第二種
        MyThread thread2 = new MyThread();
        thread2.start();
    }
}

而實際Android開發(fā)工作中,以上兩種都不用,我們通常使用Android提供的Handler和java.util包里的Executor。

Executor

Executor 是一個接口,execute執(zhí)行Runnable。

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

看下使用:

      val executor: Executor = Executors.newCachedThreadPool()
        executor.execute { }

點進去newCachedThreadPool,發(fā)現(xiàn)返回的是一個ExecutorService。ExecutorService就是Executor的實現(xiàn)了。

        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

ExecutorService

ExecutorService有兩個方法:

void shutdown();是指不再添加任務,執(zhí)行完已有任務后結束。 List<Runnable> shutdownNow();是立即調用線程的interrupt()結束所有的線程。

ThreadPoolExecutor

上面看到Executors里面new的是ThreadPoolExecutor,我們看下ThreadPoolExecutor的構造方法:

//五個參數(shù)的構造函數(shù)
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue)

//六個參數(shù)的構造函數(shù)-1
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory)

//六個參數(shù)的構造函數(shù)-2
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler)

//七個參數(shù)的構造函數(shù)
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) 
  • corePoolSize: 該線程池中核心線程數(shù)最大值

核心線程:在創(chuàng)建完線程池之后,核心線程先不創(chuàng)建,在接到任務之后創(chuàng)建核心線程。并且會一直存在于線程池中(即使這個線程啥都不干),有任務要執(zhí)行時,如果核心線程沒有被占用,會優(yōu)先用核心線程執(zhí)行任務。數(shù)量一般情況下設置為CPU核數(shù)的二倍即可。

  • maximumPoolSize: 該線程池中線程總數(shù)最大值

線程總數(shù)=核心線程數(shù)+非核心線程數(shù)。

非核心線程:簡單理解,即核心線程都被占用,但還有任務要做,就創(chuàng)建非核心線程。

  • keepAliveTime: 非核心線程閑置超時時長

這個參數(shù)可以理解為,任務少,但池中線程多,非核心線程不能白養(yǎng)著,超過這個時間不工作的就會被干掉,但是核心線程會保留。

  • TimeUnit: keepAliveTime的單位

TimeUnit是一個枚舉類型,其包括:

NANOSECONDS:1微毫秒 = 1微秒 / 1000
MICROSECONDS:1微秒 = 1毫秒 / 1000
MILLISECONDS:1毫秒 = 1秒 /1000
SECONDS:秒
MINUTES:分
HOURS:小時
DAYS:天
  • BlockingQueue workQueue: 線程池中的任務隊列

默認情況下,任務進來之后先分配給核心線程執(zhí)行,核心線程如果都被占用,并不會立刻開啟非核心線程執(zhí)行任務,而是將任務插入任務隊列等待執(zhí)行,核心線程會從任務隊列取任務來執(zhí)行,任務隊列可以設置最大值,一旦插入的任務足夠多,達到最大值,才會創(chuàng)建非核心線程執(zhí)行任務。

常見的workQueue有四種:

  1. SynchronousQueue:這個隊列接收到任務的時候,會直接提交給線程處理,而不保留它,如果所有線程都在工作怎么辦?那就新建一個線程來處理這個任務!所以為了保證不出現(xiàn)<線程數(shù)達到了maximumPoolSize而不能新建線程>的錯誤,使用這個類型隊列的時候,maximumPoolSize一般指定成Integer.MAX_VALUE,即無限大。

  2. LinkedBlockingQueue:這個隊列接收到任務的時候,如果當前已經創(chuàng)建的核心線程數(shù)小于線程池的核心線程數(shù)上限,則新建線程(核心線程)處理任務;如果當前已經創(chuàng)建的核心線程數(shù)等于核心線程數(shù)上限,則進入隊列等待。由于這個隊列沒有最大值限制,即所有超過核心線程數(shù)的任務都將被添加到隊列中,這也就導致了maximumPoolSize的設定失效,因為總線程數(shù)永遠不會超過corePoolSize

  3. ArrayBlockingQueue:可以限定隊列的長度,接收到任務的時候,如果沒有達到corePoolSize的值,則新建線程(核心線程)執(zhí)行任務,如果達到了,則入隊等候,如果隊列已滿,則新建線程(非核心線程)執(zhí)行任務,又如果總線程數(shù)到了maximumPoolSize,并且隊列也滿了,則發(fā)生錯誤,或是執(zhí)行實現(xiàn)定義好的飽和策略。

  4. DelayQueue:隊列內元素必須實現(xiàn)Delayed接口,這就意味著你傳進去的任務必須先實現(xiàn)Delayed接口。這個隊列接收到任務時,首先先入隊,只有達到了指定的延時時間,才會執(zhí)行任務。

  • ThreadFactory threadFactory -> 創(chuàng)建線程的工廠

可以用線程工廠給每個創(chuàng)建出來的線程設置名字。一般情況下無須設置該參數(shù)。

  • RejectedExecutionHandler handler -> 飽和拒絕策略

這是當任務隊列和線程池都滿了時所采取的應對策略,默認是AbordPolicy。

AbordPolicy:表示無法處理新任務,并拋出 RejectedExecutionException 異常。此外還有3種策略,它們分別如下。

CallerRunsPolicy:用調用者所在的線程來處理任務。此策略提供簡單的反饋控制機制,能夠減緩新任務的提交速度。

DiscardPolicy:不能執(zhí)行的任務,并將該任務刪除。

DiscardOldestPolicy:丟棄隊列最近的任務,并執(zhí)行當前的任務。

四種線程池

Executors類為我們提供的四種簡單創(chuàng)建線程池的方法:

private val fix = Executors.newFixedThreadPool(4)
private val cache = Executors.newCachedThreadPool()
private val single = Executors.newSingleThreadExecutor()
private val scheduled = Executors.newScheduledThreadPool(4)

其實就是調用不同的ThreadPoolExecutor的構造方法。下面一個一個分析:

  1. FixedThreadPool

        public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
        }
    

    FixedThreadPool的corePoolSize和maximumPoolSize都設置為參數(shù)nThreads,也就是只有固定數(shù)量的核心線程,不存在非核心線程。keepAliveTime為0L表示多余的線程立刻終止,因為不會產生多余的線程,所以這個參數(shù)是無效的,也就是說線程不會被回收一直保存在線程池。FixedThreadPool的任務隊列采用的是LinkedBlockingQueue。一般我們設置為cpu核心數(shù)+1。

    private val fix = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1)

    FixThreadPool其實就像一堆人排隊上公廁一樣,可以無數(shù)多人排隊,但是廁所位置就那么多,而且沒人上時,廁所閑置著也不會搬走。

  2. SingleThreadPool

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    

    我們可以看到總線程數(shù)和核心線程數(shù)都是1,所以就只有一個核心線程。該線程池才用鏈表阻塞隊列LinkedBlockingQueue,先進先出原則,所以保證了任務的按順序逐一進行。

    SingleThreadPool可以理解為公廁里只有一個坑位,先來先上。

  3. CachedThreadPool

        public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue<Runnable>());
        }
    

    CachedThreadPool的corePoolSize是0,maximumPoolSize是Int的最大值,也就是說CachedThreadPool沒有核心線程,全部都是非核心線程,并且沒有上限。keepAliveTime是60秒,就是說空閑線程等待新任務60秒,超時則銷毀。此處用到的隊列是阻塞隊列SynchronousQueue,這個隊列沒有緩沖區(qū),所以其中最多只能存在一個元素,有新的任務則阻塞等待。

    適用于頻繁IO的操作,因為他們的任務量小,但是任務基數(shù)非常龐大,使用核心線程處理的話,數(shù)量創(chuàng)建方面就很成問題。

CachedThreadPool有點像去沖浪,因為海洋無限大,隨時去都有位置沖浪,一個人沖完60秒內可以免費給下一個人玩。超過60秒沖浪板就被商家回收。

  1. ScheduledThreadPool
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }

可以看出corePoolSize是傳進來的固定值,maximumPoolSize無限大,因為采用的隊列DelayedWorkQueue是無解的,所以maximumPoolSize參數(shù)無效。如果運行的線程達到了corePoolSize時,則將任務添加到DelayedWorkQueue中。DelayedWorkQueue會將任務進行排序,先要執(zhí)行的任務會放在隊列的前面。在跟此前介紹的線程池不同的是,當執(zhí)行完任務后,會將ScheduledFutureTask中的time變量改為下次要執(zhí)行的時間并放回到DelayedWorkQueue中。

ScheduledThreadPool主要用于執(zhí)行定時任務以及有固定周期的重復任務。

Callable

Callable是java1.5添加進來的一個增強版本。類似于Runnable,卻又有差異:

  1. Runnable是自從java1.1就有了,而Callable是1.5之后才加上去的。
  2. Callable規(guī)定的方法是call(),Runnable規(guī)定的方法是run()。
  3. Callable的任務執(zhí)行后可返回值,而Runnable的任務是不能返回值(是void)。
  4. call方法可以拋出異常,run方法不可以。
  5. 運行Callable任務可以拿到一個Future對象,表示異步計算的結果。它提供了檢查計算是否完成的方法,以等待計算的完成,并檢索計算的結果。通過Future對象可以了解任務執(zhí)行情況,可取消任務的執(zhí)行,還可獲取執(zhí)行結果。
  6. 加入線程池運行,Runnable使用ExecutorService的execute方法,Callable使用submit方法。

下面看下使用:

    val executor: ExecutorService = Executors.newSingleThreadExecutor()
    val future: Future<String> = executor.submit(MyCallable())
    try {
        val string: String = future.get()
    } catch (e: ExecutionException) {

    }
    executor.shutdown()
    class MyCallable() : Callable<String> {
        override fun call(): String {
            return "done"
        }
    }

線程安全

JMM

因為硬件架構,會導致一些問題,特別在多線程的時候更為突出:

  • 緩存一致性問題:在多處理器系統(tǒng)中,每個處理器都有自己的高速緩存,而它們又共享同一主內存(MainMemory)?;诟咚倬彺娴拇鎯换ズ芎玫亟鉀Q了處理器與內存的速度矛盾,但是也引入了新的問題:緩存一致性(CacheCoherence)。當多個處理器的運算任務都涉及同一塊主內存區(qū)域時,將可能導致各自的緩存數(shù)據(jù)不一致的情況,如果真的發(fā)生這種情況,那同步回到主內存時以誰的緩存數(shù)據(jù)為準呢?為了解決一致性的問題,需要各個處理器訪問緩存時都遵循一些協(xié)議,在讀寫時要根據(jù)協(xié)議來進行操作,這類協(xié)議有MSI、MESI(IllinoisProtocol)、MOSI、Synapse、Firefly及DragonProtocol,等等。
  • 指令重排序問題:為了使得處理器內部的運算單元能盡量被充分利用,處理器可能會對輸入代碼進行亂序執(zhí)行(Out-Of-Order Execution)優(yōu)化,處理器會在計算之后將亂序執(zhí)行的結果重組,保證該結果與順序執(zhí)行的結果是一致的,但并不保證程序中各個語句計算的先后順序與輸入代碼中的順序一致。因此,如果存在一個計算任務依賴另一個計算任務的中間結果,那么其順序性并不能靠代碼的先后順序來保證。與處理器的亂序執(zhí)行優(yōu)化類似,Java虛擬機的即時編譯器中也有類似的指令重排序(Instruction Reorder)優(yōu)化。

線程間通信必須要經過主內存。

如下,如果線程A與線程B之間要通信的話,必須要經歷下面2個步驟:

1)線程A把本地內存A中更新過的共享變量刷新到主內存中去。

2)線程B到主內存中去讀取線程A之前已更新過的共享變量。

當對象和變量被存放在計算機中各種不同的內存區(qū)域中時,就可能會出現(xiàn)一些具體的問題。Java內存模型建立所圍繞的問題:在多線程并發(fā)過程中,如何處理多線程讀同步問題與可見性(多線程緩存與指令重排序)、多線程寫同步問題與原子性。

Java內存模型(即Java Memory Model,簡稱JMM)本身是一種抽象的概念,并不真實存在,它描述的是一組規(guī)則或規(guī)范,通過這組規(guī)范定義了程序中各個變量(包括實例字段,靜態(tài)字段和構成數(shù)組對象的元素)的訪問方式。

  1. 多線程讀同步與可見性 線程對共享變量修改的可見性。當一個線程修改了共享變量的值,其他線程能夠立刻得知這個修改。

  2. 原子性 指一個操作是按原子的方式執(zhí)行的。要么該操作不被執(zhí)行;要么以原子方式執(zhí)行,即執(zhí)行過程中不會被其它線程中斷。

  3. 有序性 有序性是指對于單線程的執(zhí)行代碼,我們總是認為代碼的執(zhí)行是按順序依次執(zhí)行的,這樣的理解并沒有毛病,畢竟對于單線程而言確實如此,但對于多線程環(huán)境,則可能出現(xiàn)亂序現(xiàn)象,因為程序編譯成機器碼指令后可能會出現(xiàn)指令重排現(xiàn)象,重排后的指令與原指令的順序未必一致,要明白的是,在Java程序中,倘若在本線程內,所有操作都視為有序行為,如果是多線程環(huán)境下,一個線程中觀察另外一個線程,所有操作都是無序的,前半句指的是單線程內保證串行語義執(zhí)行的一致性,后半句則指指令重排現(xiàn)象和工作內存與主內存同步延遲現(xiàn)象。

volatile

volatile關鍵字有如下兩個作用

  1. 保證被volatile修飾的共享變量對所有線程總數(shù)可見的,也就是當一個線程修改了一個被volatile修飾共享變量的值,新值總數(shù)可以被其他線程立即得知。
  2. 禁止指令重排序優(yōu)化。
//線程1
boolean stop = false;
while(!stop){
    doSomething();
}

//線程2
stop = true;

如果線程2改變了stop的值,線程1一定會停止嗎?不一定。當線程2更改了stop變量的值之后,但是還沒來得及寫入主存當中,線程2轉去做其他事情了,那么線程1由于不知道線程2對stop變量的更改,因此還會一直循環(huán)下去。

但是用volatile修飾之后就變得不一樣了:

//線程1
volatile boolean stop = false;
while(!stop){
    doSomething();
}

//線程2
stop = true;

第一:使用volatile關鍵字會強制將修改的值立即寫入主存;

第二:使用volatile關鍵字的話,當線程2進行修改時,會導致線程1的工作內存中緩存變量stop的緩存行無效(反映到硬件層的話,就是CPU的L1或者L2緩存中對應的緩存行無效);

第三:由于線程1的工作內存中緩存變量stop的緩存行無效,所以線程1再次讀取變量stop的值時會去主存讀取。

那么在線程2修改stop值時(當然這里包括2個操作,修改線程2工作內存中的值,然后將修改后的值寫入內存),會使得線程1的工作內存中緩存變量stop的緩存行無效,然后線程1讀取時,發(fā)現(xiàn)自己的緩存行無效,它會等待緩存行對應的主存地址被更新之后,然后去對應的主存讀取最新的值。

那么線程1讀取到的就是最新的正確的值

這也就是內存模型JMM的內存可見性。

   private volatile int inc = 0;

    void count() {
        inc++;
    }

    void add() {
        new Thread() {
            @Override
            public void run() {
                for (int j = 0; j < 100_00_00; j++) {
                    count();
                }
                System.out.println(inc);
            }
        }.start();
        new Thread() {
            @Override
            public void run() {
                for (int j = 0; j < 100_00_00; j++) {
                    count();
                }
                System.out.println(inc);
            }
        }.start();

    }

看這段代碼,2個線程分別加一百萬次。結果會打印出兩百萬次嗎?不會的。可能有的人就會有疑問,不對啊,上面是對變量inc進行自增操作,由于volatile保證了可見性,那么在每個線程中對inc自增完之后,在其他線程中都能看到修改后的值啊,所以有兩個線程分別進行了一百萬次操作,那么最終inc的值應該是兩百萬啊。

這里面就有一個誤區(qū)了,volatile關鍵字能保證可見性沒有錯,但是上面的程序錯在沒能保證原子性。可見性只能保證每次讀取的是最新的值,但是volatile沒辦法保證對變量的操作的原子性。

inc++; 其實是兩個步驟,先加加,然后再賦值。不是原子性操作,所以volatile不能保證線程安全。

synchronized

synchronized是Java中的關鍵字,是利用鎖的機制來實現(xiàn)同步的。Synchronized的作用主要有三個:

  1. 原子性:確保線程互斥的訪問同步代碼;
  2. 可見性:保證共享變量的修改能夠及時可見,其實是通過Java內存模型中的 “對一個變量unlock操作之前,必須要同步到主內存中;如果對一個變量進行l(wèi)ock操作,則將會清空工作內存中此變量的值,在執(zhí)行引擎使用此變量前,需要重新從主內存中l(wèi)oad操作或assign操作初始化變量值” 來保證的;
  3. 有序性:有效解決重排序問題,即 “一個unlock操作先行發(fā)生(happen-before)于后面對同一個鎖的lock操作”;

synchronized 可以修飾方法和代碼塊,進入synchronized修飾的方法或者代碼塊的線程,就會獲取monitor對象,monitor也就是Java里的對象鎖。

下面看下經典的賣票案例:

class Ticket implements Runnable {
    /* 五百張票 */
    private int tickets = 500;

    @Override
    public void run() {

        while (true) {
            //同步鎖
            synchronized (this) {
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.printf("%s窗口正在賣出第%d張票!\n", Thread.currentThread().getName(), tickets--);
                } else {
                    System.out.printf("%s窗口已售罄\n", Thread.currentThread().getName());
                    System.exit(0);
                }
            }
        }
    }
}
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Thread thread1= new Thread(ticket);
        Thread thread2 = new Thread(ticket);
        Thread thread3 = new Thread(ticket);
        thread1.start();
        thread2.start();
        thread3.start();
    }

3個線程賣500張票。利用synchronized實現(xiàn)線程安全,下面修改下實現(xiàn):

class Ticket  {
    /* 五百張票 */
    private int tickets = 500;

    public void sellTckets() {
        while (true) {
            //同步鎖
            synchronized (this) {
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.printf("%s窗口正在賣出第%d張票!\n", Thread.currentThread().getName(), tickets--);
                } else {
                    System.out.printf("%s窗口已售罄\n", Thread.currentThread().getName());
                    System.exit(0);
                }
            }
        }
    }
}
public static void main(String[] args) {
        final Ticket ticket = new Ticket();
        Thread thread1= new Thread(){
            @Override
            public void run() {
                ticket.sellTckets();
            }
        };
        Thread thread2 = new Thread(){
            @Override
            public void run() {
                ticket.sellTckets();
            }
        };
        Thread thread3 = new Thread(){
            @Override
            public void run() {
                ticket.sellTckets();
            }
        };
        thread1.start();
        thread2.start();
        thread3.start();
    }

一樣的線程安全,多線程賣票,但是現(xiàn)在我不僅要賣票,還要訂餐,賣票和訂餐是兩個互不干涉的操作,但是因為 synchronized (this)拿到的是同一個對象鎖,所以如果線程1在賣票,那么線程2就不能拿到對象鎖去訂餐:

class Ticket  {
    /* 二百張票 */
    private int tickets = 200;
    /* 二百份盒飯 */
    private int foods = 200;

    public void sell??Tckets() {
        while (true) {
            //同步鎖
            synchronized (this) {
                if (tickets > 0) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.printf("%s窗口正在賣出第%d張票!\n", Thread.currentThread().getName(), tickets--);
                } else {
                    System.out.printf("%s窗口車票已售罄\n", Thread.currentThread().getName());
                    System.exit(0);
                }
            }
        }
    }

    public void sellFoods() {
        while (true) {
            //同步鎖
            synchronized (this) {
                if (foods > 0) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.printf("%s窗口正在賣出第%d份盒飯!\n", Thread.currentThread().getName(), foods--);
                } else {
                    System.out.printf("%s窗口盒飯已售罄\n", Thread.currentThread().getName());
                    System.exit(0);
                }
            }
        }
    }

那么怎么能多線程訂票的同時,別的線程也可以訂餐呢?用不同的對象即可:

class Ticket {
    private int tickets = 200;

    private int foods = 200;
    Object object1 = new Object();
    Object object2 = new Object();

    public void sellTickets() {
        while (true) {
            //同步鎖
            synchronized (object1) {
                if (tickets > 0) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.printf("%s窗口正在賣出第%d張票!\n", Thread.currentThread().getName(), tickets--);
                } else {
                    System.out.printf("%s窗口車票已售罄\n", Thread.currentThread().getName());
                    System.exit(0);
                }
            }
        }
    }

    public void sellFoods() {
        while (true) {
            //同步鎖
            synchronized (object2) {
                if (foods > 0) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.printf("%s窗口正在賣出第%d份盒飯!\n", Thread.currentThread().getName(), foods--);
                } else {
                    System.out.printf("%s窗口盒飯已售罄\n", Thread.currentThread().getName());
                    System.exit(0);
                }
            }
        }
    }
}

這就像你家里2個臥室,門鎖是一樣的鎖所以都用同一把鑰匙。老王拿著鑰匙進入主臥反鎖了門睡覺,你想去次臥睡,但是鑰匙被老王拿進主臥了。你去不了次臥。只能等他出來把鑰匙給你。怎么能你倆都去睡覺呢?那就配兩把鑰匙。老王拿著主臥的鑰匙去了主臥,你拿著次臥的鑰匙去次臥睡。

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

友情鏈接更多精彩內容