理解Java中的synchronized關鍵字

理解Java中的synchronized [’sɪŋkrənaɪzd]

問題

有如下一個類a

class A {
    public synchronized void a() {
    }

    public synchronized void b() {
    }
}

然後創建兩個對象

A a1 = new A();
A a2 = new A();

然後在兩個線程中併發訪問如下代碼:

Thread1       a1.a();        

Thread2       a2.a();

請問二者能否構成線程同步?

如果A的定義是下面這種呢?

class A {
    public static synchronized void a() {
    }

    public static synchronized void b() {
    }
}

要點

  1. synchronized採用鎖同步的機制實現線程同步
  2. 線程在等待鎖釋放的過程中處於阻塞狀態
  3. 一把鎖可以同步所有對應的代碼

使用:

synchronized修飾代碼時會表明對應的鎖,所有表明這把鎖的代碼都會同步。

Synchronized主修修飾對象爲以下三種:

  1. 修飾普通方法 一個對象中的加鎖方法只允許一個線程訪問。但要注意這種情況下鎖的是訪問該方法的實例對象, 如果多個線程不同對象訪問該方法,則無法保證同步。

  2. 修飾靜態方法 由於靜態方法是類方法, 所以這種情況下鎖的是包含這個方法的類,也就是類對象;這樣如果多個線程不同對象訪問該靜態方法,也是可以保證同步的。

  3. 修飾代碼塊 其中普通代碼塊 如Synchronized(obj) 這裏的obj 可以爲類中的一個屬性、也可以是當前的對象,它的同步效果和修飾普通方法一樣;Synchronized方法 (obj.class)靜態代碼塊它的同步效果和修飾靜態方法類似。

Class A{
    //鎖是this
    public synchronized void a(){};
    public synchronized void b(){};
    //鎖是this.class
    public synchronized static void x(){};
    public synchronized static void y(){};
    public void z(){
        //指定鎖,該處指定爲this
        synchronized(this){
        }
    }
}

this 鎖對應object,this.class 鎖對應 clss
所以第一種a1,a2兩個鎖是不同的鎖會併發,第二種是相同的鎖A.class不會併發;

另外,修飾代碼塊時指定的鎖就與所有這把鎖對應的代碼同步。

運行機制

簡單的說是這樣synchronized在實現同步功能時會聲明一把鎖,表明必須持有這把鎖的線程才能訪問執行,在一個線程需要執行這塊代碼時,會先嚐試持有這把指定的鎖,如果這個鎖已被別的線程持有,那麼該線程就會開始等待,直到這把鎖被釋放後纔會繼續執行,並且在執行時會一直持有這把鎖,以防止其他線程進入這把鎖控制的所有代碼,直到該線程執行完纔會釋放這把鎖。

上面說的很模糊,這個鎖是怎麼回事呢,看了這個synchronized底層語義原理之後大概意思就是說synchronized的對象鎖,鎖的指針指向的是monitor對象,每個對象有都存在着一個 monitor(管程) 與之關聯,在 java 虛擬機 hotSpot 中monitor於對象的關聯關係是由ObjectMonitor實現。
這裏寫圖片描述
其中有_EntryList 和_WaitSet 兩個對象列表,多線程訪問synchronized修飾的代碼段時會先進入_EntryList,線程獲取到monitor之後進入 Owner 區域,monitor中的 count 會加一,線程調用 wait 方法之後會釋放 monitor,count–,線程進入 waitset等待喚醒(被其他線程調用notify/notifyAll),線程執行完畢也要是釋放monitor並復位。我注意到這個地方講到wait和鎖的關係,又想到以前面試被問過sleep() 和wait()的區別

sleep() 和wait()的區別

我當初提前看過一些面試題,就是這樣答的:

sleep()方法屬於Thread[θrɛd]類中的。而wait()方法是屬於Object類中的。sleep()是線程安全的,wait()線程不安全。
真正理解之後的回答:
sleep()方法導致了程序暫停執行指定的時間,讓出cpu該其他線程,但是他一直在持有monitor(未進入 Waitset中),當指定的時間到了又會自動恢復運行狀態。這就跟上面連到一起了

而當調用wait()方法的時候,線程會進入Waitset,釋放 monitor,進入等待此對象的Waitset(等待鎖定池),只有針對此對象調用notify/notifyAll方法後本線程才進入對象鎖定池準備。

notify和notifyall的區別

notify只會通知一個在等待的對象,而notifyAll會通知所有在等待的對象,並且所有對象都會繼續運行

wait此方法導致當前線程(稱之爲 T)將其自身放置在對象的等待集中Waitset(等待鎖定池),然後放棄此對象上的所有同步要求。

出於線程調度目的,在發生以下四種情況之一前,線程 T 被禁用,且處於休眠狀態:

  1. 其他某個線程調用此對象的 notify 方法,並且線程 T 碰巧被任選爲被喚醒的線程。
  2. 其他某個線程調用此對象的 notifyAll 方法。
  3. 其他某個線程中斷線程 T。大約已經到達指定的實際時間。但是,如果 timeout 爲零,則不考慮實際時間,在獲得通知前該線程將一直等待。

然後,從對象的等待集Waitset中刪除線程T,並重新進行線程調度(進入_EntryList)。然後,該線程以常規方式與其他線程競爭,以獲得在該對象上同步的權利;一旦獲得對該對象的控制權,該對象上的所有其同步聲明都將被恢復到以前的狀態。

這就是調用 wait方法時的情況。然後,線程 T 從 wait 方法的調用中返回。所以,從 wait 方法返回時,該對象和線程 T 的同步狀態與調用 wait 方法時的情況完全相同。

就是說多個線程等待的時候notifyall所有的等待線程都被通知了,這些線程進行競爭之後其中一個會得到鎖(也是要等待正在執行的線程先釋放鎖才行)

這是轉的Hongten的一個例子

/**
 * 
 */
package com.b510.test;

/**
 * java中的sleep()和wait()的區別
 * @author Hongten
 * @date 2013-12-10
 */
public class TestD {

    public static void main(String[] args) {
        new Thread(new Thread1()).start();
        try {
            Thread.sleep(5000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(new Thread2()).start();
    }

    private static class Thread1 implements Runnable{
        @Override
        public void run(){
            synchronized (TestD.class) {
            System.out.println("enter thread1...");    
            System.out.println("thread1 is waiting...");
            try {
                //調用wait()方法,線程會放棄對象鎖,進入等待此對象的等待鎖定池
                TestD.class.wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("thread1 is going on ....");
            System.out.println("thread1 is over!!!");
            }
        }
    }

    private static class Thread2 implements Runnable{
        @Override
        public void run(){
            synchronized (TestD.class) {
                System.out.println("enter thread2....");
                System.out.println("thread2 is sleep....");
                //只有針對此對象調用notify()方法後本線程才進入對象鎖定池準備獲取對象鎖進入運行狀態。
                TestD.class.notify();
                //==================
                //區別
                //如果我們把代碼:TestD.class.notify();給註釋掉,即TestD.class調用了wait()方法,但是沒有調用notify()
                //方法,則線程永遠處於掛起狀態。
                try {
                    //sleep()方法導致了程序暫停執行指定的時間,讓出cpu該其他線程,
                    //但是他的監控狀態依然保持者,當指定的時間到了又會自動恢復運行狀態。
                    //在調用sleep()方法的過程中,線程不會釋放對象鎖。
                    Thread.sleep(5000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("thread2 is going on....");
                System.out.println("thread2 is over!!!");
            }
        }
    }
}
enter thread1...
thread1 is waiting...
enter thread2....
thread2 is sleep....
thread2 is going on....
thread2 is over!!!
thread1 is going on ....
thread1 is over!!!

註釋TestD.class.notify();

enter thread1...
thread1 is waiting...
enter thread2....
thread2 is sleep....
thread2 is going on....
thread2 is over!!!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章