Synchronized關(guān)鍵字詳解(對(duì)object對(duì)象持鎖)

1.Synchronized對(duì)象監(jiān)視器為Object時(shí)的使用
1)Synchronized修飾方法時(shí),持有當(dāng)前對(duì)象的鎖,當(dāng)有多個(gè)對(duì)象時(shí),不同的對(duì)象有不同對(duì)象的“監(jiān)視器”。
2)當(dāng)Synchronized修飾類(lèi)中的一個(gè)方法,而該類(lèi)中的另一個(gè)方法沒(méi)被Synchronized修飾時(shí),這種情況下當(dāng)A線程先調(diào)用被Synchronized修飾的方法時(shí),A線程就先持有object對(duì)象的Lock鎖,但是B線程可以異步調(diào)用object對(duì)象中的非Synchronized類(lèi)型的方法。
如果B線程在A線程調(diào)用Synchronized方法后再調(diào)用Synchronized方法,就需要等待,也就是同步。

2.臟讀:發(fā)生臟讀的情況是在讀取實(shí)例變量時(shí),此值已經(jīng)被其他線程更改過(guò)了。
以下是一個(gè)臟讀情況:PublicVar類(lèi)中setValue被synchronized修飾,但是getValue沒(méi)被synchronized修飾,在setValue的方法中將username修改后使線程sleep5000ms。這時(shí)username被更改但是userpassword沒(méi)有被更改,此時(shí)主線程中的getValue()讀取了數(shù)據(jù)讀到的是被更改的username “B” 和沒(méi)被更改的userpassword “AA”。

public class PublicVar{
    public String username = "A";
    public String password = "AA";
    synchronized public void setValue(String username,String password){
        try {
            this.username = username;
            Thread.sleep(5000);
            this.password = password;
            System.out.println("setValue method thread name=" + Thread.currentThread().getName() + " username=" + username + " password=" + password);
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void getValue(){
        System.out.println("getValue method thread name=" + Thread.currentThread().getName() + " username=" + username + " password=" + password);
    }
}
public class ThreadA extends Thread{
    private PublicVar publicVar;
    public ThreadA(publicVar publicVar){
        super();
        this.publicVar = publicVar;
    }
    @Override
    public void run(){
        super.run();
        publicVar.setValue("B","BB");
    } 
}
public class Test{
    public static void main(String[] args){
        try{
            PublicVar publicVarRef = new PublicVar();
            ThreadA thread = new ThreadA(publicVarRef);
            thread.start();
            thread.sleep(200);
            publicVarRef.getValue();
        } catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

該臟讀的解決方法就是在getValue()方法前也加上synchronized關(guān)鍵字,這樣一來(lái)當(dāng)A線程調(diào)用anyObject對(duì)象加入synchronized關(guān)鍵字的X方法時(shí),A線程就獲得了X方法所在對(duì)象的鎖,所以其他線程必須等A線程執(zhí)行完畢才可以調(diào)用X方法,而B(niǎo)線程如果調(diào)用聲明了synchronized關(guān)鍵字的非X方法時(shí),必須等A線程將X方法執(zhí)行完,也就是釋放對(duì)象鎖后才可以調(diào)用。這時(shí)A已經(jīng)執(zhí)行完一個(gè)完整的任務(wù),也就是說(shuō)username和userpassword這兩個(gè)實(shí)例變量已經(jīng)同時(shí)被賦值,不存在臟讀的基本環(huán)境。

3.synchronized鎖重入:關(guān)鍵字synchronized擁有鎖重入的功能,也就是在使用synchronized時(shí),當(dāng)一個(gè)線程得到一個(gè)對(duì)象鎖后,再次請(qǐng)求此對(duì)象鎖時(shí)是可以再次得到該對(duì)象的鎖的。這也證明在一個(gè)synchronized方法/塊的內(nèi)部調(diào)用本類(lèi)的其他synchronized方法/塊時(shí),是永遠(yuǎn)可以得到鎖的。
子類(lèi)是完全可以通過(guò)“可重入鎖”調(diào)用父類(lèi)的同步方法的。
“可重入鎖”的概念是:自己可以再次獲取自己的內(nèi)部鎖。比如有1條線程獲得了某個(gè)對(duì)象的鎖,此時(shí)這個(gè)對(duì)象鎖還沒(méi)有釋放,當(dāng)其再次想要獲取這個(gè)對(duì)象的鎖的時(shí)候還是可以獲取的,如果鎖不可重入的話就會(huì)造成死鎖。
(底層原理是JVM的管程來(lái)實(shí)現(xiàn)詳細(xì)可以看另一篇博客)

4.出現(xiàn)異常,鎖自動(dòng)釋放:當(dāng)一個(gè)線程執(zhí)行代碼出現(xiàn)異常時(shí),其所持有的鎖會(huì)自動(dòng)釋放。(底層也是管程來(lái)實(shí)現(xiàn):如果一個(gè)同步方法執(zhí)行期間拋出了異常,并且在方法內(nèi)部無(wú)法處理此異常,那么這個(gè)同步方法所持有的管程將在異常拋到同步方法之外時(shí)自動(dòng)釋放)。

5.同步不具有繼承性:父類(lèi)方法有synchronized關(guān)鍵字,子類(lèi)重寫(xiě)該方法不帶synchronized關(guān)鍵字,那么子類(lèi)該方法就不是同步。

6.synchronized方法的缺點(diǎn)及synchronized代碼塊的優(yōu)點(diǎn):synchronized關(guān)鍵字修飾方法的缺點(diǎn)就在于,當(dāng)該方法需要長(zhǎng)時(shí)間執(zhí)行時(shí),另一個(gè)線程需要等待持有線程執(zhí)行完才能允許。 synchronized修飾代碼塊就可以解決這個(gè)缺點(diǎn),當(dāng)兩個(gè)并發(fā)線程訪問(wèn)同一個(gè)對(duì)象object中的synchronized(this)同步代碼塊時(shí),一段時(shí)間內(nèi)只有一個(gè)線程被執(zhí)行,另一個(gè)線程必須等待當(dāng)前線程執(zhí)行完這個(gè)代碼后才能執(zhí)行該代碼。也就是說(shuō)在方法內(nèi)同步代碼塊外的部分可以異步執(zhí)行。那么將長(zhǎng)時(shí)間執(zhí)行的一部分代碼置于同步代碼塊外就能異步執(zhí)行這一耗時(shí)較長(zhǎng)的代碼塊,從而縮短運(yùn)行時(shí)間。

7.synchronized代碼塊:關(guān)于synchronized(this)需要注意的是,當(dāng)一個(gè)線程訪問(wèn)object的一個(gè)synchronized(this)同步代碼塊時(shí),其他線程對(duì)同一個(gè)object中所有其他synchronized(this)同步代碼塊的訪問(wèn)將被阻塞,這說(shuō)明synchronized使用的“對(duì)象監(jiān)視器”是一個(gè)。和synchronized方法一樣,synchronized(this)代碼也是鎖定當(dāng)前對(duì)象。

8.將任意對(duì)象作為對(duì)象監(jiān)視器:多個(gè)線程調(diào)用同一個(gè)對(duì)象中的不同名稱(chēng)的synchronized同步方法或synchronized(this)同步代碼塊時(shí),調(diào)用的效果就是按順序執(zhí)行,也就是同步的,阻塞的。
這說(shuō)明synchronized同步方法或synchronized(this)同步代碼塊分別有兩種作用
(1)synchronized同步方法
1)對(duì)其他synchronized同步方法或synchronized(this)同步代碼塊調(diào)用呈阻塞狀態(tài)。
2)同一時(shí)間只有一個(gè)線程可以執(zhí)行synchronized同步方法中的代碼。
(2)synchronized(this)同步代碼塊
1)對(duì)其他synchronized同步方法或synchronized(this)同步代碼塊調(diào)用呈阻塞狀態(tài)。
2)同一時(shí)間只有一個(gè)線程可以執(zhí)行synchronized(this)同步方法中的代碼。

     synchronized(非this對(duì)象x)同步代碼塊
     1)在多個(gè)線程持有“對(duì)象監(jiān)視器”為同一對(duì)象的前提下,同一時(shí)間只有一個(gè)線程可以執(zhí)行synchronized(非this對(duì)象x)同步代碼塊。
     2)當(dāng)持有“對(duì)象監(jiān)視器”為同一個(gè)對(duì)象的前提下,同一時(shí)間只有一個(gè)線程可以執(zhí)行synchronized(非this對(duì)象x)同步代碼塊中的代碼。
    鎖非this對(duì)象具有一定的優(yōu)點(diǎn):如果在一個(gè)類(lèi)中有很多個(gè)synchronized方法,這時(shí)雖然能實(shí)現(xiàn)同步,但會(huì)受到阻塞,所以影響運(yùn)行效率;但如果使用同步代碼塊鎖非this對(duì)象,則synchronized(非this)代碼塊中的程序與同步方法是異步的,不與其他鎖this同步方法爭(zhēng)搶this鎖,則可大大提高運(yùn)行效率。

9.下面是一個(gè)兩個(gè)synchronized同步方法之間的臟讀現(xiàn)象:main線程開(kāi)始執(zhí)行時(shí)Thread1先執(zhí)行并進(jìn)入if (list.getSize() < 1) 判斷語(yǔ)句中,并進(jìn)入睡眠,但是另一個(gè)線程可以異步也進(jìn)入if (list.getSize() < 1) 這個(gè)判斷語(yǔ)句中那么這兩個(gè)就都可以add(data)。

因?yàn)樗鼈兂钟械氖遣煌膶?duì)象監(jiān)視器:同步代碼塊放在非同步synchronized方法中進(jìn)行聲明,并不能保證調(diào)用方法的線程的執(zhí)行同步/順序性,也就是線程調(diào)用方法的順序是無(wú)序的,雖然在同步塊中執(zhí)行的順序是同步的,這樣極易出現(xiàn)臟讀問(wèn)題。

public class MyOneList{
    private List list = new ArrayList();
    synchronized public void add(String data){
        list.add(data);
    }
    synchronized public int getSize(){
        return list.size();
    }
}
public class MyService {
    public MyOneList addServiceMethod(MyOneList list,String data){
        try {
            if (list.getSize() < 1) {
                Thread.sleep(2000);
                list.add(data);
            }
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        return list;
    }   
}
public class MyThread1 extends Thread{
    private MyOneList list;
    public MyThread1(MyOneList list){
        super();
        this.list = list;
    }
    @Override
    public void run(){
        MyService msRef = new MyService();
        msRef.addServiceMethod(list,"A");
    }
}
public class MyThread2 extends Thread{
    private MyOneList list;
    public MyThread2(MyOneList list){
        super();
        this.list = list;
    }
    @Override
    public void run(){
        MyService msRef = new MyService();
        msRef.addServiceMethod(list,"B");
    }
}
public class run{
    public static void main(String[] args) throws InterruptedException {
        MyOneList list = new MyOneList();
        MyThread1 thread1 = new MyThread1(list);
        thread1.setName("A");
        thread1.start();
        MyThread2 thread2 = new MyThread2(list);
        thread2.setName("B");
        thread2.start();
        Thread.sleep(6000);
        System.out.println("listSize=" + list.getSize());
    }
}

使用同步代碼塊可以避免上述臟讀現(xiàn)象,添加synchronized (list) 可以使兩個(gè)線程持有同一個(gè)對(duì)象監(jiān)視器:

public class MyService {
    public MyOneList addServiceMethod(MyOneList list,String data){
        try {
            synchronized (list) {
                if (list.getSize() < 1) {
                    Thread.sleep(2000);
                }
            }
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        return list;
    }
}
 “synchronized (非list對(duì)象x) ”格式的寫(xiě)法是將x對(duì)象本身作為“對(duì)象監(jiān)視器”,這樣就可以得出以下3個(gè)結(jié)論:
1)當(dāng)多個(gè)線程同時(shí)執(zhí)行synchronized (x){}同步代碼塊時(shí)呈同步效果。
2)當(dāng)其他線程執(zhí)行x對(duì)象中synchronized同步方法時(shí)呈同步效果。
3)當(dāng)其他線程執(zhí)行x對(duì)象方法里面的synchronized (this)代碼塊時(shí)也是呈現(xiàn)同步效果。但需要注意:如果其他線程調(diào)用不加synchronized關(guān)鍵字的方法時(shí),還是異步調(diào)用。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,899評(píng)論 0 11
  • 一:java概述:1,JDK:Java Development Kit,java的開(kāi)發(fā)和運(yùn)行環(huán)境,java的開(kāi)發(fā)工...
    ZaneInTheSun閱讀 2,815評(píng)論 0 11
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類(lèi)相關(guān)的語(yǔ)法,內(nèi)部類(lèi)的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 34,734評(píng)論 18 399
  • 本文出自 Eddy Wiki ,轉(zhuǎn)載請(qǐng)注明出處:http://eddy.wiki/interview-java.h...
    eddy_wiki閱讀 2,299評(píng)論 0 14
  • 1.解決信號(hào)量丟失和假喚醒 public class MyWaitNotify3{ MonitorObject m...
    Q羅閱讀 1,015評(píng)論 0 1

友情鏈接更多精彩內(nèi)容