1. 程式人生 > 其它 >執行緒同步機制 ★★★★

執行緒同步機制 ★★★★

執行緒同步機制★★★★

1、synchronized-執行緒同步

執行緒同步機制的語法是:

synchronized(){
	// 執行緒同步程式碼塊。
}

synchronized()小括號內容是至關重要的,它必須得是要同步的多個執行緒所共享的資料


舉個例子:

假設目前程式內共有t1到t5五個執行緒,我只想讓t1,t2,t3排隊,其餘執行緒不排隊,要怎麼辦呢?

一定要在()中寫一個t1 t2 t3共享的物件。而這個物件對於t4 t5來說不是共享的。

在java語言中,任何一個物件都有“一把鎖”,其實這把鎖就是標記。100個物件,100把鎖。1個物件1把鎖。


2、eg.理解執行緒同步

/**
 * 通過程式碼來理解執行緒同步synchronized
 * 多執行緒共享同一銀行賬戶,取錢
 */
public class Test {
    public static void main(String[] args) {
        // 建立賬戶物件(只建立1個)
        Account act = new Account("act-001", 10000);
        // 建立兩個執行緒,共享同一個物件
        Thread t1 = new Account.AccountThread(act);
        Thread t2 = new Account.AccountThread(act);

        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }
    static class Account {
        private String actno;//賬戶編號
        private double balance;//賬戶餘額

        //物件
        Object o = new Object(); // 例項變數。(Account物件是多執行緒共享的,Account物件中的例項變數obj也是共享的。)

        public Account() {
        }

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

        public String getActno() {
            return actno;
        }

        public void setActno(String actno) {
            this.actno = actno;
        }

        public double getBalance() {
            return balance;
        }

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

        //取款方法
        public void withdraw(double money) {
            /**
             * 以下可以共享,金額不會出錯
             * 以下這幾行程式碼必須是執行緒排隊的,不能併發。
             * 一個執行緒把這裡的程式碼全部執行結束之後,另一個執行緒才能進來。
             */
            synchronized (this) {
                //synchronized(actno) {
                //synchronized(o) {

                /**
                 * 以下不共享,金額會出錯
                 */
           //Object obj2 = new Object();
           //synchronized(obj2) { // 這樣編寫就不安全了。因為obj2不是共享物件。
           //String s = null;
           //synchronized(null) {//編譯不通過
           //synchronized(s) {//java.lang.NullPointerException
                double before = this.getBalance();
                double after = before - money;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.setBalance(after);
                //}
            }
        }

        static class AccountThread extends Thread {
            // 兩個執行緒必須共享同一個賬戶物件。
            private Account act;

            // 通過構造方法傳遞過來賬戶物件
            public AccountThread(Account act) {
                this.act = act;
            }

            public void run() {
                double money = 5000;
                act.withdraw(money);
                System.out.println(Thread.currentThread().getName() + "對" + act.getActno() + "取款" + money + "成功,餘額" + act.getBalance());
            }
        }
    }
}

如何判斷是否共享?

  1. 如果某個物件是多個執行緒所共享的,那麼該物件中的例項變數自然也是這幾個執行緒所共享的,如程式碼中的下圖部分

 /**
 * 以下可以共享,金額不會出錯
 * 以下這幾行程式碼必須是執行緒排隊的,不能併發。
 * 一個執行緒把這裡的程式碼全部執行結束之後,另一個執行緒才能進來。
 */
	synchronized (this)
	synchronized(actno)
	synchronized(o)
  1. 在新方法中建立的例項變數不屬於線上程方法中定義的物件Account的例項變數,這些例項變數自然就不是多個執行緒所共享的

2、執行結果

觀察以下幾張圖片的執行結果,證明物件或例項變數是否共享

以上程式碼鎖this、例項變數actno、例項變數o都可以!因為這三個被多個執行緒共享

共享



檢視結果可知,t1,t2兩個執行緒排隊對act-001賬戶物件進行取款操作,說明this為多執行緒共享



檢視結果可知,t1,t2兩個執行緒排隊對act-001賬戶物件進行取款操作,說明例項變數actno為多執行緒共享



檢視結果可知,t1,t2兩個執行緒排隊對act-001賬戶物件進行取款操作,說明例項變數o為多執行緒共享


不共享


用同樣的方式測試後發現,不屬於執行緒方法中定義的物件Account的例項變數都是不共享的

obj2,null,s都不共享


3、執行緒同步原理

  • 假設t1和t2執行緒併發,開始執行程式碼的時候,肯定有一個先一個後

  • 假設t1先執行了,遇到了synchronized,這個時候自動找“共享物件”的物件鎖
    找到之後,並佔有這把鎖,然後執行同步程式碼塊中的程式,在程式執行過程中一直都是
    佔有這把鎖的。直到同步程式碼塊程式碼結束,這把鎖才會釋放。

  • 假設t1已經佔有這把鎖,此時t2也遇到synchronized關鍵字,也會去佔有共享物件的這把鎖

    結果這把鎖被t1佔有,t2只能在同步程式碼塊外面等待t1的結束
    直到t1把同步程式碼塊執行結束了,t1會歸還這把鎖,此時t2終於等到這把鎖,然後
    t2佔有這把鎖之後,進入同步程式碼塊執行程式。

    這樣就實現了執行緒排隊執行。


4、在例項方法中可以使用synchronized(不推薦)

  • 舉個例子,在上邊的程式碼中,synchronized是可以在例項方法中使用的,但是,此時的鎖只能是this!沒得挑,只能是this,不能是其他的物件。所以這種方式不靈活

  • synchronized出現在例項方法上,表示整個方法體都需要同步,可能會無故擴大同步的範圍,導致程式的執行效率降低。所以這種方式不常用。但這種方式可以精簡程式碼。

  • 如果共享的物件就是this,並且需要同步的程式碼塊是整個方法體,建議使用這種方式。


1、eg.

在例項方法中使用synchronized

    public synchronized void withdraw(double money){
        double before = this.getBalance();
        double after = before - money;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setBalance(after);
    }

在方法呼叫處使用synchronized

    public void run(){
        double money = 5000;
        // 取款
        // 多執行緒併發執行這個方法。
        //synchronized (this) { //這裡的this是AccountThread物件,這個物件不共享!
        synchronized (act) { // 這種方式也可以,只不過擴大了同步的範圍,效率更低了。
            act.withdraw(money);
        }

        System.out.println(Thread.currentThread().getName() + "對"+act.getActno()+"取款"+money+"成功,餘額" + act.getBalance());
    }

2、疑問(待解決)


為什麼this不共享?執行發現也沒有問題啊?待解決