synchronized解決原子性-synchronized的三種應(yīng)用方式(實(shí)例講解)

前言

????上一節(jié)講了i++并不是線程安全的,我們需要用synchronized來保證其線程安全。

????這里我就介紹下synchronized的基本用法和簡單原理。

????便于說明,我寫了個(gè)i++的例子:

public class AddI {
    public static volatile int i = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> add(1000000));
        Thread t2 = new Thread(() -> add(1000000));

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println(i);
    }

    public static void add(int n) {
        for (int m = 0; m < n; m++) {
            i++;
        }
    }
}



1、什么時(shí)候加鎖呢?

????沒有共享就沒有傷害,比如上面的i++被2個(gè)線程同時(shí)修改,出現(xiàn)了并發(fā)問題。此時(shí)我們就需要進(jìn)行加鎖。

????如果一個(gè)變量沒有共享,且沒有并發(fā)問題,那加鎖只會(huì)降低程序的性能。

????線程安全是并發(fā)編程中的重要關(guān)注點(diǎn),應(yīng)該注意到的是,造成線程安全問題的主要誘因有兩點(diǎn),一是存在共享數(shù)據(jù)(也稱臨界資源),二是存在多條線程共同操作共享數(shù)據(jù)。
????因此為了解決這個(gè)問題,我們可能需要這樣一個(gè)方案,當(dāng)存在多個(gè)線程操作共享數(shù)據(jù)時(shí),需要保證同一時(shí)刻有且只有一個(gè)線程在操作共享數(shù)據(jù),其他線程必須等到該線程處理完數(shù)據(jù)后再進(jìn)行,這種方式有個(gè)高尚的名稱叫互斥鎖,即能達(dá)到互斥訪問目的的鎖,也就是說當(dāng)一個(gè)共享數(shù)據(jù)被當(dāng)前正在訪問的線程加上互斥鎖后,在同一個(gè)時(shí)刻,其他線程只能處于等待的狀態(tài),直到當(dāng)前線程處理完畢釋放該鎖。
????在 Java 中,關(guān)鍵字 synchronized可以保證在同一個(gè)時(shí)刻,只有一個(gè)線程可以執(zhí)行某個(gè)方法或者某個(gè)代碼塊(主要是對方法或者代碼塊中存在共享數(shù)據(jù)的操作),同時(shí)我們還應(yīng)該注意到synchronized另外一個(gè)重要的作用,synchronized可保證一個(gè)線程的變化(主要是共享數(shù)據(jù)的變化)被其他線程所看到(保證可見性,完全可以替代Volatile功能),這點(diǎn)確實(shí)也是很重要的。



2、Synchronized三種應(yīng)用方式

常見的用法:

class X {
    // 方式一:修飾非靜態(tài)方法
    synchronized void foo() {
        // 臨界區(qū)
    }
    // 方式二:修飾靜態(tài)方法
    synchronized static void bar() {
        // 臨界區(qū)
    }
    // 方式三:修飾代碼塊
    Object obj = new Object();
    void baz() {
        synchronized(obj) {
            // 臨界區(qū)
        }
    }
} 

????在java里面使用synchronized,加鎖lock解鎖unlock這2個(gè)操作是Java默認(rèn)加上的,Java 編譯器會(huì)在 synchronized 修飾的方法或代碼塊前后自動(dòng)加上加鎖 lock() 和解鎖 unlock(),這樣做的好處就是加鎖 lock() 和解鎖 unlock() 一定是成對出現(xiàn)的,畢竟忘記解鎖 unlock() 可是個(gè)致命的 Bug。

????那 synchronized 里的加鎖 lock() 和解鎖 unlock() 鎖定的對象在哪里呢?上面的代碼我們看到只有修飾代碼塊的時(shí)候,鎖定了一個(gè) obj 對象,那修飾方法的時(shí)候鎖定的是什么呢?這個(gè)也是 Java 的一條隱式規(guī)則:

  • 當(dāng)修飾靜態(tài)方法的時(shí)候,鎖定的是當(dāng)前類的 Class 對象,在上面的例子中就是 Class X;
  • 當(dāng)修飾非靜態(tài)方法的時(shí)候,鎖定的是當(dāng)前實(shí)例對象 this。

對于上面的例子,synchronized 修飾靜態(tài)方法相當(dāng)于:

class X {
    // 修飾靜態(tài)方法
    synchronized(X.class) static void bar() {
        // 臨界區(qū)
    }
}

修飾非靜態(tài)方法,相當(dāng)于:

class X {
    // 修飾非靜態(tài)方法
    synchronized(this) void foo() {
        // 臨界區(qū)
    }
}

這里把i++的例子改下就沒有并發(fā)問題了:

public class AddI {
    public static volatile int i = 0;
    Object object = new Object();// 單獨(dú)new一個(gè)對象 用于加鎖

    public static void main(String[] args) throws InterruptedException {
        // 方式一:修飾靜態(tài)方法
        Thread t1 = new Thread(() -> add(1000000));
        Thread t2 = new Thread(() -> add(1000000));

        // 方式二:修飾普通方法
        /*AddI addI = new AddI();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                addI.add2(1000000);
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                addI.add2(1000000);
            }
        });*/

        // 方式三
        /*AddI addI = new AddI();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                addI.add3(1000000);
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                addI.add3(1000000);
            }
        });*/

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println(i);
    }

    // 修飾靜態(tài)方法
    public static synchronized void add1(int n) {
        for (int m = 0; m < n; m++) {
            i++;
        }
    }

    // 修飾普通方法
    public synchronized void add2(int n) {
        for (int m = 0; m < n; m++) {
            i++;
        }
    }

    // 代碼塊加鎖
    public void add3(int n) {
        synchronized (object) {
            for (int m = 0; m < n; m++) {
                i++;
            }
        }
    }
}

synchronized 是 Java 在語言層面提供的互斥原語,其實(shí) Java 里面還有很多其他類型的鎖,但作為互斥鎖,原理都是相通的:鎖,一定有一個(gè)要鎖定的對象,至于這個(gè)鎖定的對象要保護(hù)的資源以及在哪里加鎖 / 解鎖,就屬于設(shè)計(jì)層面的事情了。

加鎖本質(zhì)就是在鎖的對象的對象頭中寫入當(dāng)前線程id(這涉及到底層的東西,后面我也整理一篇)。



滬漂程序員一枚。
堅(jiān)持寫博客,如果覺得還可以的話,給個(gè)小星星哦,你的支持就是我創(chuàng)作的動(dòng)力。

個(gè)人微信公眾號:“Java尖子生”,閱讀更多干貨。
關(guān)注公眾號,領(lǐng)取學(xué)習(xí)、面試資料。加技術(shù)討論群。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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