1. 程式人生 > >Java多執行緒(一)執行緒基礎

Java多執行緒(一)執行緒基礎

1.執行緒與程序

程序: 是併發執行的程式在執行過程中分配和管理資源的基本單位,是一個動態概念,程序是系統中獨立存在的實體,擁有自己獨立的資源,擁有自己私有的地址空間。程序的實質,就是程式在多道程式系統中的一次執行過程,它是動態產生,動態消亡的,具有自己的生命週期和各種不同的狀態。程序具有併發性,它可以同其他程序一起併發執行,按各自獨立的、不可預知的速度向前推進 程序由程式、資料和程序控制塊三部分組成。

執行緒: 執行緒,有時候被稱之為輕量級執行緒,是程式執行流的最小單元。執行緒是程序中的一個實體,是被系統獨立排程和分派的基本單位,執行緒自己不擁有系統資源,只擁有一點兒在執行中必不可少的資源,但它可與同屬一個程序的其它執行緒共享程序所擁有的全部資源。一個執行緒可以建立和撤消另一個執行緒,同一程序中的多個執行緒之間可以併發執行。由於執行緒之間的相互制約,致使執行緒在執行中呈現出間斷性。每一個程式都至少有一個執行緒,若程式只有一個執行緒,那就是程式本身。 一個標準的執行緒由執行緒ID,當前指令指標(PC),暫存器集合和堆疊組成。

多執行緒 執行緒是程式中一個單一的順序控制流程。在單個程式中同時執行多個執行緒完成不同的工作,稱為多執行緒。

2.執行緒的實現

java中現成的實現一般有四種方式

2.1 繼承Thread類

2.2 實現Runnable介面

2.3 使用Callable和Future介面建立執行緒

因為這個我們平時用的不多,這個做個簡單的demo展示。

這種建立方式的流程是建立Callable介面的實現類,並實現call()方法。並使用FutureTask類來包裝Callable實現類的物件,且以此FutureTask物件作為Thread物件的target來建立執行緒。

  • 使用例項
package cn.ji2h.othertest;

import cn.ji2h.util.LogUtil;

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

public class CallableDemo {
    public static void main(String[] args){
        Callable<Integer> myCallable = new MyCallable();
        FutureTask<Integer> ft = new FutureTask<Integer>(myCallable);

        Thread thread = new Thread(ft);
        thread.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        LogUtil.logger.info("執行緒 " + Thread.currentThread().getName() + " is running!");

        try {
            int sum = ft.get();
            LogUtil.logger.info("sum的結果是:" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        LogUtil.logger.info("end!");

    }

}

class MyCallable implements Callable<Integer>{

    //與run方法不同的是,call方法具有返回值
    @Override
    public Integer call() throws Exception {
        LogUtil.logger.info("執行緒 " + Thread.currentThread().getName() + " is running!");
        Thread.sleep(10000);
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            sum += i;
        }
        return sum;
    }
}
  • 執行結果:
2018-10-02 00:35:41,424 [Thread-0] INFO  cn.ji2h.util.LogUtil - 執行緒 Thread-0 is running!
2018-10-02 00:35:42,284 [main] INFO  cn.ji2h.util.LogUtil - 執行緒 main is running!
2018-10-02 00:35:51,431 [main] INFO  cn.ji2h.util.LogUtil - sum的結果是:4950
2018-10-02 00:35:51,431 [main] INFO  cn.ji2h.util.LogUtil - end!

程式碼分析: 在實現Callable介面中,此時不再是run()方法了,而是call()方法,此call()方法作為執行緒執行體,同時還具有返回值!在建立新的執行緒時,是通過FutureTask來包裝MyCallable物件,同時作為了Thread物件的target。FutureTask類的定義:

public class FutureTask<V> implements RunnableFuture<V>

FutureTask類實現了RunnableFuture介面,我們看一下RunnableFuture介面的實現:

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

於是,我們發現FutureTask類實際上是同時實現了Runnable和Future介面,由此才使得其具有Future和Runnable雙重特性。通過Runnable特性,可以作為Thread物件的target,而Future特性,使得其可以取得新建立執行緒中的call()方法的返回值。

執行下此程式,我們發現sum = 4950永遠都是最後輸出的。那麼為什麼sum =4950會永遠最後輸出呢?原因在於通過ft.get()方法獲取子執行緒call()方法的返回值時,當子執行緒此方法還未執行完畢,ft.get()方法會一直阻塞,直到call()方法執行完畢才能取到返回值。

2.4 各種執行緒池啟動執行緒

略,後續會有專題說明

3.執行緒的狀態和生命週期

3.1 執行緒的狀態

java執行緒的五種基本狀態

  • 新建狀態:當執行緒物件對建立後,即進入了新建狀態,如:Thread t = new MyThread()。

  • 就緒狀態:當呼叫執行緒物件的start()方法(t.start();),執行緒即進入就緒狀態。處於就緒狀態的執行緒,只是說明此執行緒已經做好了準備,隨時等待CPU排程執行,並不是說執行了t.start()此執行緒立即就會執行。

  • 執行狀態:當CPU開始排程處於就緒狀態的執行緒時,此時執行緒才得以真正執行,即進入到執行狀態。注:就緒狀態是進入到執行狀態的唯一入口,也就是說,執行緒要想進入執行狀態執行,首先必須處於就緒狀態中。

  • 阻塞狀態:處於執行狀態中的執行緒由於某種原因,暫時放棄對CPU的使用權,停止執行,此時進入阻塞狀態,直到其進入到就緒狀態,才有機會再次被CPU呼叫以進入到執行狀態。根據阻塞產生的原因不同,阻塞狀態又可以分為三種:

    • 等待阻塞-執行狀態中的執行緒執行wait()方法,使本執行緒進入到等待阻塞狀態。
    • 同步阻塞-執行緒在獲取synchronized同步鎖失敗(因為鎖被其它執行緒所佔用),它會進入同步阻塞狀態。
    • 其它阻塞-通過呼叫執行緒的sleep()或join()或發出了I/O請求時,執行緒會進入到阻塞狀態。當sleep()狀態超時、join()等待執行緒終止或者超時、或者I/O處理完畢時,執行緒重新轉入就緒狀態。
  • 死亡狀態:執行緒執行完了或者因異常退出了run()方法,該執行緒結束生命週期。

3.2 執行緒的生命週期

執行緒的生命週期 請結合圖片和3.1接內容進行檢視

4.執行緒的常用方法

4.1 sleep():執行緒休眠

sleep() 的作用是讓當前執行緒休眠,即當前執行緒會從“執行狀態”進入到“休眠(阻塞)狀態”。sleep()會指定休眠時間,執行緒休眠的時間會大於/等於該休眠時間;線上程重新被喚醒時,它會由“阻塞狀態”變成“就緒狀態”,從而等待cpu的排程執行。常用來暫停程式的執行。 sleep()方法不會釋放鎖。

4.2 yield():執行緒讓步

讓出cpu重新競爭,執行緒讓步。它能讓當前執行緒暫停,但不會阻塞該執行緒,而是由“執行狀態”進入到“就緒狀態”,從而讓其它具有相同優先順序的等待執行緒獲取執行。因此,使用yield()的目的是讓相同優先順序的執行緒之間能適當的輪轉執行。但是,並不能保證在當前執行緒呼叫yield()之後,其它具有相同優先順序的執行緒就一定能獲得執行權,也有可能是當前執行緒又進入到“執行狀態”繼續執行! yield()方法不會釋放鎖。

  • 例項程式碼
package cn.ji2h.othertest;

import cn.ji2h.util.LogUtil;

public class YieldDemo {
    public static void main(String[] args){
        ThreadYield yt1 = new ThreadYield("A");
        ThreadYield yt2 = new ThreadYield("B");
        yt1.start();
        yt2.start();
    }
}

class ThreadYield extends Thread{

    public ThreadYield(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i <=10 ; i++) {
            LogUtil.logger.info(this.getName() + "--------" + i);

            if (i == 3){
                this.yield();
            }
        }
    }
}
  • 執行情況 第一種情況:A執行緒當執行到30時會將CPU時間讓掉,這時B執行緒搶到CPU時間並執行。 第二種情況:A執行緒當執行到30時會將CPU時間讓掉,這時A執行緒搶到CPU時間並執行。 第三種情況:B執行緒當執行到30時會將CPU時間讓掉,這時A執行緒搶到CPU時間並執行。 第四種情況:B執行緒當執行到30時會將CPU時間讓掉,這時B執行緒搶到CPU時間並執行。

4.3 isAlive():執行緒判活

方法isAlive()功能是判斷當前執行緒是否處於活動狀態。活動狀態就是執行緒啟動且尚未終止,比如正在執行或準備開始執行。

4.4 interrupt():執行緒中斷

4.4.1 interrupt簡述

interrupt()單詞本身的含義是中斷、終止、阻斷。當某個執行緒收到這個訊號(命令)的時候,會將自生的狀態屬性置為“interrupted”,但是執行緒本身並不會立刻終止。程式設計師需要根據這個狀態屬性,自行決定如何進行執行緒的下一步活動。

我們經常通過判斷執行緒的中斷標記來控制執行緒,但需要注意的是interrupt,並不是執行緒處於任何狀態,都可以接收interrupt訊號。如果在收到interrupt訊號時,執行緒處於阻塞狀態(wait()、wait(time)或者sleep引起的),那麼執行緒將會丟擲InterruptedException異常:

  1. 當執行緒處於"執行"狀態的時候,其執行緒物件中的isinterrupt屬性被置為true。
  2. 當執行緒處於"阻塞"狀態的時候,丟擲InterruptedException異常。注意,如果丟擲了InterruptedException異常,那麼其isinterrupt屬性不會被置為true。任然是false
  • 例項程式碼
package cn.ji2h.othertest;

public class InterruptDemo {

    public static void main(String[] args) throws Exception {
        // thread one執行緒
        Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                Thread currentThread = Thread.currentThread();
                // 並不是執行緒收到interrupt訊號,就會立刻種種;
                // 執行緒需要檢查自生狀態是否正常,然後決定下一步怎麼走。
                while(!currentThread.isInterrupted()) {
                    /*
                     * 這裡列印一句話,說明迴圈一直在執行
                     * 但是正式系統中不建議這樣寫程式碼,因為沒有中斷(wait、sleep)的無限迴圈是非常耗費CPU資源的
                     * */
                    System.out.println("Thread One 一直在執行!");
                }

                System.out.println("Thread One 正常結束!" + currentThread.isInterrupted());
            }
        });

        // thread two執行緒
        Thread threadTwo = new Thread(new Runnable() {
            @Override
            public void run() {
                Thread currentThread = Thread.currentThread();
                while(!currentThread.isInterrupted()) {
                    synchronized (currentThread) {
                        try {
                            // 通過wait進入阻塞
                            currentThread.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace(System.out);
                            System.out.println("Thread Two 由於中斷訊號,異常結束!" + currentThread.isInterrupted());
                            return;
                        }
                    }
                }

                System.out.println("Thread Two 正常結束!");
            }
        });

        threadOne.start();
        threadTwo.start();
        // 您可以通過eclipse工具在這裡打上端點,以保證threadOne和threadTwo完成了啟動
        // 當然您還可以使用其他方式來確保這個事情
        System.out.println("兩個執行緒正常執行,現在開始發出中斷訊號");
        threadOne.interrupt();
        threadTwo.interrupt();
    }

}

上面的示例程式碼中,我們建立了兩個執行緒threadOne和threadTwo。其中threadOne執行緒在沒有任何阻塞的情況下一直迴圈執行(雖然這種方式在正式環境中不建議使用,但是這裡我們是為了模擬這種執行緒執行狀態),每迴圈一次都檢測該執行緒的isInterrupt屬性的值,如果發現值為true則終止迴圈;另一個threadTwo執行緒,在啟動後馬上進入阻塞狀態,等待喚醒(實際上沒有其他執行緒會喚醒它,以便模擬執行緒阻塞的狀態)。

  • 執行結果
Thread One 一直在執行!
Thread One 一直在執行!
兩個執行緒正常執行,現在開始發出中斷訊號
Thread One 一直在執行!
Thread One 正常結束!true
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at cn.ji2h.othertest.InterruptDemo$2.run(InterruptDemo.java:34)
	at java.lang.Thread.run(Thread.java:748)
Thread Two 由於中斷訊號,異常結束!false

threadOne執行緒的isInterrupt屬性被成功置為true,迴圈正常結束執行緒執行正常完成;而threadTwo執行緒由於處於wait()引起的阻塞狀態,所以在收到interrupt訊號後,丟擲了異常,其isInterrupt屬性依然是false;

4.4.2 thread.isInterrupted()和Thread.interrupted()的區別

在Java的執行緒基本操作方法中,有兩種方式獲取當前執行緒的isInterrupt屬性。一種是物件方法thread.isInterrupted(),另一種是Thread類的靜態方法Thread.interrupted()。這兩個方法看似相同,實際上是有區別的,我們來看看Java的Thread類的這部分原始碼:

public class Thread implements Runnable {
    ......

    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

    public boolean isInterrupted() {
        return isInterrupted(false);
    }

    /**
     * Tests if some Thread has been interrupted.  The interrupted state
     * is reset or not based on the value of ClearInterrupted that is
     * passed.
     */
    private native boolean isInterrupted(boolean ClearInterrupted);

    ......
}

可以看到,物件方法的thread.isInterrupted()和靜態方法的Thread.interrupted()都是呼叫的JNI底層的isInterrupted()方法。但是區別在於這個ClearInterrupted引數,前者傳入的false,後者傳入的是true。相信各位讀者都已經猜出其中的含義了,ClearInterrupted引數向作業系統層指明是否在獲取狀態後將當前執行緒的isInterrupt屬性重置為(或者叫恢復,或者叫清除)false。

這就意味著當某個執行緒的isInterrupt屬性成功被置為true後,如果您使用物件方法thread.isInterrupted()獲取值,無論您獲取多少次得到的返回值都是true;但是如果您使用靜態方法Thread.interrupted()獲取值,那麼只有第一次獲取的結果是true,隨後執行緒的isInterrupt屬性將被恢復成false,後續無論使用Thread.interrupted()呼叫還是使用thread.isInterrupted()呼叫,獲取的結果都是false。

4.5 join():執行緒禮讓

讓一個執行緒等待另一個執行緒完成才繼續執行。如A執行緒執行體中呼叫B執行緒的join()方法,則A執行緒被阻塞,直到B執行緒執行完為止,轉換為就緒狀態,得到CPU之後,A才能得以繼續執行。

4.6 wait()、notify()、notifyAll():執行緒通訊

  1. wait() 導致當前執行緒等待並使其進入到等待阻塞狀態。直到其他執行緒呼叫該同步鎖物件的notify()或notifyAll()方法來喚醒此執行緒。
  2. notify() 喚醒在此同步鎖物件上等待的單個執行緒,如果有多個執行緒都在此同步鎖物件上等待,則會任意選擇其中某個執行緒進行喚醒操作,只有當前執行緒放棄對同步鎖物件的鎖定,才可能執行被喚醒的執行緒。
  3. notifyAll() 喚醒在此同步鎖物件上等待的所有執行緒,只有當前執行緒放棄對同步鎖物件的鎖定,才可能執行被喚醒的執行緒。
  • 使用例項
package cn.ji2h.othertest;

public class WaitDemo {
    public static void main(String[] args) {
        Account account = new Account("123456", 0);

        Thread drawMoneyThread = new DrawMoneyThread("取錢執行緒", account, 700);
        Thread depositeMoneyThread = new DepositeMoneyThread("存錢執行緒", account, 700);

        drawMoneyThread.start();
        depositeMoneyThread.start();
    }
}

class Account{
    private String accountNo;
    private double balance;
    // 標識賬戶中是否已有存款
    private boolean flag = false;

    public Account() {

    }

    public Account(String accountNo, double balance) {
        this.accountNo = accountNo;
        this.balance = balance;
    }

    public String getAccountNo() {
        return accountNo;
    }

    public void setAccountNo(String accountNo) {
        this.accountNo = accountNo;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    /**
     * 存錢
     *
     * @param depositeAmount
     */
    public synchronized void deposite(double depositeAmount, int i) {

        if (flag) {
            // 賬戶中已有人存錢進去,此時當前執行緒需要等待阻塞
            try {
                System.out.println(Thread.currentThread().getName() + " 開始要執行wait操作" + " -- i=" + i);
                wait();
                // 1
                System.out.println(Thread.currentThread().getName() + " 執行了wait操作" + " -- i=" + i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            // 開始存錢
            System.out.println(Thread.currentThread().getName() + " 存款:" + depositeAmount + " -- i=" + i);
            setBalance(balance + depositeAmount);
            flag = true;

            // 喚醒其他執行緒
            notifyAll();

            // 2
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "-- 存錢 -- 執行完畢" + " -- i=" + i);
        }
    }

    /**
     * 取錢
     *
     * @param drawAmount
     */
    public synchronized void draw(double drawAmount, int i) {
        if (!flag) {
            // 賬戶中還沒人存錢進去,此時當前執行緒需要等待阻塞
            try {
                System.out.println(Thread.currentThread().getName() + " 開始要執行wait操作" + " -- i=" + i);
                wait();
                System.out.println(Thread.currentThread().getName() + " 執行了wait操作" + " -- i=" + i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            // 開始取錢
            System.out.println(Thread.currentThread().getName() + " 取錢:" + drawAmount + " -- i=" + i);
            setBalance(getBalance() - drawAmount);

            flag = false;

            // 喚醒其他執行緒
            notifyAll();

            System.out.println(Thread.currentThread().getName() + "-- 取錢 -- 執行完畢" + " -- i=" + i); // 3
        }
    }
}
class DrawMoneyThread extends Thread{

    private Account account;
    private double amount;

    public DrawMoneyThread(String threadName, Account account, double amount) {
        super(threadName);
        this.account = account;
        this.amount = amount;
    }

    public void run() {
        for (int i = 0; i < 5; i++) {
            account.draw(amount, i);
        }
    }

}

class DepositeMoneyThread extends Thread{

    private Account account;
    private double amount;

    public DepositeMoneyThread(String threadName, Account account, double amount) {
        super(threadName);
        this.account = account;
        this.amount = amount;
    }

    public void run() {
        for (int i = 0; i < 5; i++) {
            account.deposite(amount, i);
        }
    }

}
  • 執行結果
取錢執行緒 開始要執行wait操作 -- i=0
存錢執行緒 存款:700.0 -- i=0
存錢執行緒-- 存錢 -- 執行完畢 -- i=0
存錢執行緒 開始要執行wait操作 -- i=1
取錢執行緒 執行了wait操作 -- i=0
取錢執行緒 取錢:700.0 -- i=1
取錢執行緒-- 取錢 -- 執行完畢 -- i=1
取錢執行緒 開始要執行wait操作 -- i=2
存錢執行緒 執行了wait操作 -- i=1
存錢執行緒 存款:700.0 -- i=2
存錢執行緒-- 存錢 -- 執行完畢 -- i=2
存錢執行緒 開始要執行wait操作 -- i=3
取錢執行緒 執行了wait操作 -- i=2
取錢執行緒 取錢:700.0 -- i=3
取錢執行緒-- 取錢 -- 執行完畢 -- i=3
取錢執行緒 開始要執行wait操作 -- i=4
存錢執行緒 執行了wait操作 -- i=3
存錢執行緒 存款:700.0 -- i=4
存錢執行緒-- 存錢 -- 執行完畢 -- i=4
取錢執行緒 執行了wait操作 -- i=4

1.wait()方法執行後,當前執行緒立即進入到等待阻塞狀態,其後面的程式碼不會執行; 2.notify()/notifyAll()方法執行後,將喚醒此同步鎖物件上的(任意一個-notify()/所有-notifyAll())執行緒物件,但是,此時還並沒有釋放同步鎖物件,也就是說,如果notify()/notifyAll()後面還有程式碼,還會繼續執行,直到當前執行緒執行完畢才會釋放同步鎖物件; 3.notify()/notifyAll()執行後,如果下面有sleep()方法,則會使當前執行緒進入到阻塞狀態,但是同步物件鎖沒有釋放,依然自己保留,那麼一定時候後還是會繼續執行此執行緒,接下來同2; 4.wait()/notify()/nitifyAll()完成執行緒間的通訊或協作都是基於相同物件鎖的,因此,如果是不同的同步物件鎖將失去意義,同時,同步物件鎖最好是與共享資源物件保持一一對應關係; 5.當wait執行緒喚醒後並執行時,是接著上次執行到的wait()方法程式碼後面繼續往下執行的。

上面的例子相對來說比較簡單,只是為了簡單示例wait()/notify()/noitifyAll()方法的用法,但其本質上說,已經是一個簡單的生產者-消費者模式了。

5.執行緒的優先順序

java中的執行緒優先順序的範圍是1~10,預設的優先順序是5。每個執行緒預設的優先順序都與建立它的父執行緒具有相同的優先順序。預設情況下,mian執行緒具有普通優先順序。“高優先順序執行緒”會優先於“低優先順序執行緒”執行。Thread提供了setPriority(int newPriority)和getPriority()方法來設定和返回執行緒優先順序。 需要注意的是,優先順序只能保證儘可能的優先執行,不能保證絕對優先執行,這個和具體的作業系統排程有關係。

Thread類有3個靜態常量:

 MAX_PRIORITY = 10
 MIN_PRIORITY = 1
 NORM_PRIORITY = 5

6.執行緒的同步

java允許多執行緒併發控制,當多個執行緒同時操作一個可共享的資源變數時(如資料的增刪改查),將會導致資料不準確,相互之間產生衝突,因此加入同步鎖以避免在該執行緒沒有完成操作之前,被其他執行緒的呼叫,從而保證了該變數的唯一性和準確性。

保證執行緒同步需要新增synchronized欄位。

6.1 synchronized可標註的位置

在JAVA中synchronized關鍵字可以載入很多位置。您可以在一個方法定義上加synchronized關鍵字、也可以在方法體中加synchronized關鍵字、還可以在static塊中加synchronized關鍵字。

// 程式碼片段1
static {
    synchronized(ThreadLock.class) {
    }
}
// 程式碼片段2
public synchronized void someMethod() {
}
// 程式碼片段3
public synchronized static void someMethod() {
}
// 程式碼片段4
public static void someMethod() {
    synchronized (ThreadLock.class) {
    }
}
// 程式碼片段5
public void someMethod() {
    synchronized (ThreadLock.class) {
    }
}

但是不同位置的synchronized的關鍵字,代表的含義是不一樣的。synchronized(){}這個寫法,開發人員可以指定需要檢查的物件鎖。但是當synchronized載入在方法上時,有的讀者就感覺有點混淆了。這裡詳細說明一下:

synchronized關鍵字載入在非靜態方法上時: 其代表的含義和synchronized(this){}的意義相同。即對所擁有這個方法的物件進行物件鎖狀態檢查。 synchronized關鍵字載入在靜態方法上時: 其代表的含義和synchronized(Class.class)的意義相類似。即對所擁有這個方法的類的物件進行物件鎖狀態檢查(類本身也是一個物件哦 _

  • 使用例項
package cn.ji2h.othertest;

import cn.ji2h.util.LogUtil;

public class SynchronizedDemo {

    public static void main(String[] args){
        Thread ticket1 = new Thread(new Ticket());
        Thread ticket2 = new Thread(new Ticket());
        Thread ticket3 = new Thread(new Ticket());
        ticket1.start();
        ticket2.start();
        ticket3.start();
    }
}

class Ticket implements Runnable{

    private static volatile int ticket = 10;

    @Override
    public void run() {
        synchronized (Ticket.class){
            for(int i = 0; i< 20; i++){
                if(ticket > 0){
                    ticket --;
                    LogUtil.logger.info("現在還有票數: " + ticket);
                }
            }
        }

    }
}
  • 執行結果

不加synchronized的執行結果

2018-10-02 11:59:16,432 [Thread-2] INFO  cn.ji2h.util.LogUtil - 現在還有票數: 7
2018-10-02 11:59:16,433 [Thread-1] INFO  cn.ji2h.util.LogUtil - 現在還有票數: 7
2018-10-02 11:59:16,434 [Thread-1] INFO  cn.ji2h.util.LogUtil - 現在還有票數: 5
2018-10-02 11:59:16,435 [Thread-1] INFO  cn.ji2h.util.LogUtil - 現在還有票數: 4
2018-10-02 11:59:16,435 [Thread-1] INFO  cn.ji2h.util.LogUtil - 現在還有票數: 3
2018-10-02 11:59:16,435 [Thread-1] INFO  cn.ji2h.util.LogUtil - 現在還有票數: 2
2018-10-02 11:59:16,435 [Thread-1] INFO  cn.ji2h.util.LogUtil - 現在還有票數: 1
2018-10-02 11:59:16,435 [Thread-1] INFO  cn.ji2h.util.LogUtil - 現在還有票數: 0
2018-10-02 11:59:16,433 [Thread-0] INFO  cn.ji2h.util.LogUtil - 現在還有票數: 7
2018-10-02 11:59:16,434 [Thread-2] INFO  cn.ji2h.util.LogUtil - 現在還有票數: 6

加synchronized的執行結果

2018-10-02 11:55:51,315 [Thread-0] INFO  cn.ji2h.util.LogUtil - 現在還有票數: 9
2018-10-02 11:55:51,323 [Thread-0] INFO  cn.ji2h.util.LogUtil - 現在還有票數: 8
2018-10-02 11:55:51,324 [Thread-0] INFO  cn.ji2h.util.LogUtil - 現在還有票數: 7
2018-10-02 11:55:51,324 [Thread-0] INFO  cn.ji2h.util.LogUtil - 現在還有票數: 6
2018-10-02 11:55:51,326 [Thread-0] INFO  cn.ji2h.util.LogUtil - 現在還有票數: 5
2018-10-02 11:55:51,326 [Thread-0] INFO  cn.ji2h.util.LogUtil - 現在還有票數: 4
2018-10-02 11:55:51,327 [Thread-0] INFO  cn.ji2h.util.LogUtil - 現在還有票數: 3
2018-10-02 11:55:51,327 [Thread-0] INFO  cn.ji2h.util.LogUtil - 現在還有票數: 2
2018-10-02 11:55:51,327 [Thread-0] INFO  cn.ji2h.util.LogUtil - 現在還有票數: 1
2018-10-02 11:55:51,328 [Thread-0] INFO  cn.ji2h.util.LogUtil - 現在還有票數: 0

7. 參考連結