1. 程式人生 > >008.多執行緒-synchronized

008.多執行緒-synchronized

為了解決執行緒安全問題,
我們的做法是:不要讓多個執行緒同時對一個全域性變數作寫的操作。

常用的做法就是加鎖,來實現執行緒的同步。
自動鎖synchronized和手動鎖lock。
由於synchronized不需要手動釋放鎖,丟擲異常也可自動釋放鎖,
故而常用synchronized自動鎖。

一個執行緒拿到鎖後,其他執行緒則只能排隊,等待鎖的釋放。
程式碼執行完畢或者程式丟擲異常,鎖均會被釋放。

synchronized 程式碼塊

    /**
     * 相當於同步函式
     */
    public void test1_() {
        synchronized (this
) { for (int i = 1; i < 500; i++) { System.out.println("test1_..." + i); } } }
    /**
     * 相當於靜態同步函式
     */
    public void test1s_() {
        synchronized (Test.class) {
            for (int i = 1; i < 500; i++) {
                System.out.
println("test1s_..." + i); } } }
	synchronized(lock-object){
	}

括號後面要跟一個物件,
這個物件充當鎖的作用。
Java中,全部都是物件,不相同的常量字串也是不同的物件。

同步函式: (例項物件鎖)

在方法上修飾synchronized

    public synchronized void test1() {
        for (int i = 1; i < 500; i++) {
            System.out.println("test1..."
+ i); } }

靜態同步函式: (類物件鎖)
方法上加上static關鍵字,使用synchronized 關鍵字修飾
或者使用 類.class 檔案。

    public static synchronized void test1s() {
        for (int i = 1; i < 500; i++) {
            System.out.println("test1..." + i);
        }
    }
    public void test1s_() {
        synchronized (Test.class) {
            for (int i = 1; i < 500; i++) {
                System.out.println("test1s_..." + i);
            }
        }
    }

若類物件被鎖,則類物件的所有同步方法全部被鎖。
若例項物件被鎖,則該例項物件的所有同步方法全部被鎖。
不是同一個物件,例項物件鎖沒有約束。

synchronized程式碼塊的優勢:

  1. 只對需要同步的程式碼進行同步
  2. 與wait() 、notify() 、notifyAll() 一起使用時,比較方便
package cn.qbz.thread;

public class WaitNotifyTest {


    public static void main(String[] args) {
        new Thread(new Produce()).start();
        new Thread(new Consumer()).start();
    }
}

class Produce implements Runnable {

    public void run() {
        int count = 5;
        while (count > 0) {
            synchronized ("lock-object") { //此處可以鎖定任何物件,只要鎖定Produce和Consumer中的物件一樣
                System.out.println("A");
                count--;
                "lock-object".notify();// 喚醒因為呼叫物件的wait()而等待的執行緒

                if (count > 0) {
                    try {
                        "lock-object".wait();//釋放本執行緒的物件鎖,釋放CPU
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }

}

class Consumer implements Runnable {

    public void run() {
        int count = 5;
        while (count > 0) {
            synchronized ("lock-object") {

                System.out.println("B");
                count--;
                "lock-object".notify(); // 喚醒因為呼叫物件的wait()而等待的執行緒

                if (count > 0) {
                    try {
                        "lock-object".wait(); //釋放本執行緒的物件鎖,釋放CPU
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

wait():
釋放佔有的物件鎖,執行緒進入等待池,釋放cpu。
其他正在等待的執行緒即可搶佔此鎖,獲得鎖的執行緒即可執行程式。
sleep():
執行緒休眠,休眠期間,釋放cpu,但不釋放佔有的物件鎖。
即:休眠期間,其他執行緒依然無法進入此同步程式碼塊內。
notify():
喚醒因為呼叫物件的wait()而等待的執行緒。
呼叫notify()後,並不會立即釋放鎖,
而是直到synchronized程式碼塊中全部執行完畢,才釋放鎖。
JVM會在等待的執行緒中排程一個執行緒去獲得此鎖,執行程式碼。
notifyAll()
喚醒所有等待的執行緒。

notify與notifyAll
鎖池:
假設執行緒A已經擁有了某個物件鎖(非類鎖),
此時想獲取此物件鎖的其他執行緒,將進入此物件的鎖池中,
參與下次鎖的競爭。
等待池:
假設執行緒A呼叫了物件鎖的wait()方法,
執行緒A會釋放該物件鎖,並進入此物件的等待池中。
等待池中的執行緒不會參與物件鎖的競爭。
notify呼叫後,只會將等待池中的一個隨機執行緒移到鎖池中。
notifyAll呼叫後,會將全部執行緒移到鎖池中。

notify有一定機率造成死鎖。