溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java中如何使用synchronized關(guān)鍵字

發(fā)布時(shí)間:2021-07-23 15:46:49 來(lái)源:億速云 閱讀:172 作者:Leah 欄目:云計(jì)算

今天就跟大家聊聊有關(guān)Java中如何使用synchronized關(guān)鍵字,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。


一、使用synchronized關(guān)鍵字修飾實(shí)例方法
     在我們的Java中,每個(gè)對(duì)象都有一把鎖和兩個(gè)隊(duì)列,一個(gè)用于掛起未獲得鎖的線程,一個(gè)用于掛起條件不滿足而不得不等待的線程。而我們的synchronized實(shí)際上也就是一個(gè)加鎖和釋放鎖的集成。先看個(gè)例子:

public class Counter {
    private int count;

    public synchronized int getCount(){return this.count;}

    public synchronized void addCount(){this.count++;}
}
public class MyThread extends Thread{

    public static Counter counter = new Counter();

    @Override
    public void run(){
        try {
            Thread.sleep((int)(Math.random()*100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        counter.addCount();
    }
}
public static void main(String[] args){
        Thread[] threads = new Thread[100];
        for (int i=0;i<100;i++){
            threads[i] = new MyThread();
            threads[i].start();
        }

        for (int j=0;j<100;j++){
            threads[j].join();
        }

        System.out.println(MyThread.counter.getCount());
    }

上述程序無(wú)論運(yùn)行多少次,結(jié)果都是一樣的。

Java中如何使用synchronized關(guān)鍵字

這是一個(gè)典型的使用synchronized關(guān)鍵字修飾實(shí)例方法來(lái)解決競(jìng)態(tài)條件問(wèn)題的示例。首先在我們定義的線程類中,我們定義了一個(gè)Counter實(shí)例,然后讓以后的每個(gè)線程在運(yùn)行的時(shí)候都先隨機(jī)睡眠,然后調(diào)用這個(gè)公共變量count的自增方法,只不過(guò)該自增方法是有synchronized關(guān)鍵字修飾的。我們說(shuō)過(guò)每個(gè)對(duì)象都有鎖和兩個(gè)隊(duì)列,這里的count實(shí)例就是一個(gè)對(duì)象,這一百個(gè)線程每次在睡醒之后都要調(diào)用count的addCount方法,而所有要調(diào)用addCount方法的線程都必須先獲得count這個(gè)對(duì)象的鎖,也就是說(shuō),如果有一個(gè)線程獲取了count對(duì)象的鎖并開(kāi)始調(diào)用addCount方法時(shí),其他線程都得阻塞在該對(duì)象的一個(gè)隊(duì)列上,等待獲得鎖的線程執(zhí)行結(jié)束釋放鎖。

所以,在同一時(shí)刻,只可能有一個(gè)線程獲得count的鎖并對(duì)其進(jìn)行自增操作,其他的線程都在該對(duì)象的阻塞隊(duì)列上進(jìn)行等待,自然是不會(huì)出現(xiàn)多個(gè)線程在某個(gè)時(shí)間段同時(shí)操作同一個(gè)變量而引起該變量數(shù)據(jù)值不正確的情況。

二、使用synchronized關(guān)鍵字修飾靜態(tài)方法
     對(duì)于靜態(tài)方法,其實(shí)和實(shí)例方法是類似的。只不過(guò)synchronized關(guān)鍵字對(duì)實(shí)例方法而言,它獲得的是實(shí)例對(duì)象的鎖,所有共享相同該對(duì)象的線程都必須先獲得該對(duì)象的鎖。而對(duì)于靜態(tài)方法而言,synchronized關(guān)鍵字獲得的是類的鎖,也就是對(duì)于所有需要訪問(wèn)相同類的線程都是需要先獲得該類的鎖的,否則將需要在某個(gè)阻塞隊(duì)列上進(jìn)行等待。

public class MyThread extends Thread{

    public static int count;

    public synchronized static void addCount(){
        count++;
    }
    @Override
    public void run(){
        try {
            Thread.sleep((int)(Math.random()*100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        addCount();
    }
}
public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[100];
        for (int i=0;i<100;i++){
            threads[i] = new MyThread();
            threads[i].start();
        }

        for (int j=0;j<100;j++){
            threads[j].join();
        }

        System.out.println(MyThread.count);
    }

程序基本和我們的第一個(gè)例子相差無(wú)幾,在線程類中我們定義了一個(gè)靜態(tài)變量和一個(gè)靜態(tài)方法,該方法被synchronized關(guān)鍵字修飾,然后run方法依然是讓當(dāng)前線程隨機(jī)睡眠,然后調(diào)用這個(gè)被synchronized關(guān)鍵字修飾的靜態(tài)方法。我們可以看到,無(wú)論運(yùn)行多少次的程序,結(jié)果都是一樣。

Java中如何使用synchronized關(guān)鍵字

每個(gè)線程在睡醒之后,都要去調(diào)用addCount方法,而調(diào)用該方法前提是要獲取到類Count的鎖,如果獲取不到就必須在該對(duì)象的阻塞隊(duì)列上進(jìn)行等待。所以一次只會(huì)有一個(gè)線程調(diào)用addCount方法,自然是無(wú)論運(yùn)行多少次,結(jié)果都會(huì)是100。

三、使用synchronized關(guān)鍵字修飾代碼塊
     使用synchronized關(guān)鍵字修飾一段代碼塊和上述介紹的兩種情況略微有點(diǎn)不同。對(duì)于實(shí)例方法,synchronized關(guān)鍵字總是嘗試去獲取某個(gè)對(duì)象的鎖,對(duì)于靜態(tài)方法,synchronized關(guān)鍵字始終嘗試去獲取某個(gè)類的鎖,而對(duì)于我們的代碼塊,它就需要顯式指定以誰(shuí)為鎖了。例如:

public class MyThread extends Thread{

    public static Integer count = 0;

    @Override
    public void run(){
        try {
            Thread.sleep((int)(Math.random()*100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (count){
            count++;
        }
    }
}

在我們定義的線程類中,我們定義了一個(gè)靜態(tài)變量count,而每個(gè)線程在醒來(lái)之后都會(huì)去嘗試著去獲取該對(duì)象的鎖,如果得不到就阻塞在該對(duì)象的阻塞隊(duì)列上等待鎖的釋放。實(shí)際上這里的synchronized關(guān)鍵字利用的就是對(duì)象count的鎖,我們上述介紹的兩種形式,synchronized關(guān)鍵字修飾在實(shí)例方法和靜態(tài)方法上,默認(rèn)利用的是類對(duì)象的鎖和類的鎖。例如:

public synchronized void show(){....}

調(diào)用show方法等價(jià)于:

synchronized(this){
    public void show(){...}
}

而對(duì)于靜態(tài)方法:

public class A{
    public synchronized static void show(){....}
}

等價(jià)于:

synchronized(A.class){
    public static void show(){....}
}

四、使用synchronized關(guān)鍵字解決內(nèi)存可見(jiàn)性問(wèn)題
     通過(guò)了解了synchronized應(yīng)用的三種不同場(chǎng)景,我們對(duì)它應(yīng)該有了大致的一個(gè)了解。下面我們使用它解決上篇提到的多線程的一個(gè)問(wèn)題 ----- 內(nèi)存可見(jiàn)性問(wèn)題。至于競(jìng)態(tài)條件問(wèn)題已經(jīng)在第一小節(jié)間接的進(jìn)行介紹了,此處不再贅述。這里我們?cè)俸?jiǎn)單重復(fù)下內(nèi)存可見(jiàn)性問(wèn)題,因?yàn)槲覀兊腃PU是有緩存的,所以當(dāng)一個(gè)線程在運(yùn)行的時(shí)候,有些變量值的修改并沒(méi)有立馬寫回內(nèi)存,而是緩存在各級(jí)緩存中,這就導(dǎo)致其他線程訪問(wèn)這個(gè)公共變量的時(shí)候就拿不到最新的值,因此導(dǎo)致數(shù)據(jù)的值偏差,計(jì)算結(jié)果不準(zhǔn)確。我們看看一個(gè)例子:

public class MyThread extends Thread{

    public static int count = 0;

    @Override
    public void run(){
        while (count==0){
            
        }
        System.out.println("mythread exit");
    }
}
public static void main(String[] args) throws InterruptedException {
        Thread thread = new MyThread();
        thread.start();

        Thread.sleep(1000);

        MyThread.count = 1;
        System.out.println(MyThread.count);
        System.out.println("exit main");

    }

我們?cè)诙x的線程類中定義了一個(gè)共享變量,run方法主要的工作是循環(huán)等待count不為0,而我們?cè)趍ain線程中修改了這個(gè)count的值,由于循環(huán)這個(gè)操作是比較頻繁的判斷條件的,所以該線程并不會(huì)每次都從內(nèi)存中取出count的值,而是在它的緩存中取,所以主線程對(duì)count的修改,在thread線程中是始終看不見(jiàn)的。所以我們的程序輸出的結(jié)果如下:

Java中如何使用synchronized關(guān)鍵字

主線程在修改count的值之后,輸出顯示的確count的值為1,然后主線程退出,但是我們發(fā)現(xiàn)程序卻沒(méi)有結(jié)束,thread的退出信息也沒(méi)有被打印。也就是說(shuō)線程thread還被困在了while循環(huán)中,雖然main線程已經(jīng)修改了count的值。這就是內(nèi)存可見(jiàn)性問(wèn)題,主要是由于多線程之間進(jìn)行通訊的橋梁是內(nèi)存,而各個(gè)線程內(nèi)部又有各自的緩存,如果對(duì)公共變量的的修改沒(méi)有及時(shí)更新到內(nèi)存的話,那么就很容易導(dǎo)致其他線程訪問(wèn)的是數(shù)據(jù)不是最新的。

我們使用synchronized關(guān)鍵字解決上述問(wèn)題:

public class MyThread extends Thread{

    public static int count = 0;

    public synchronized static int returnCount(){return count;}
    
    @Override
    public void run(){
        while(returnCount()==0){

        }
        System.out.println("mythread exit");
    }
}

我們使用synchronized關(guān)鍵修飾了一個(gè)方法,該方法返回count的值。jvm對(duì)synchronized的兩條規(guī)定,其一是線程在解鎖之前必須把所有共享變量刷新到內(nèi)存中,其二是線程在釋放鎖的時(shí)候?qū)⑶蹇账械木彺嫫仁贡揪€程在使用該共享變量的時(shí)候從內(nèi)存中去讀取。這樣就可以保證每次對(duì)共享變量的讀取都是最新的。

當(dāng)然如果僅僅是為了解決內(nèi)存可見(jiàn)性問(wèn)題而使用synchronized關(guān)鍵字的話,會(huì)有點(diǎn)大材小用。畢竟synchronized的成本開(kāi)銷相對(duì)而言是較大的。Java中提供了一個(gè)volatile關(guān)鍵字用于解決這種內(nèi)存可見(jiàn)性問(wèn)題。例如:

public static volatile int count = 0;

看完上述內(nèi)容,你們對(duì)Java中如何使用synchronized關(guān)鍵字有進(jìn)一步的了解嗎?如果還想了解更多知識(shí)或者相關(guān)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI