synchronized鎖住的是代碼還是對象

synchronized的三種應用方式
synchronized關(guān)鍵字最主要有以下3種應用方式:

修飾實例方法,作用于當前實例加鎖,進入同步代碼前要獲得當前實例的鎖

修飾靜態(tài)方法,作用于當前類對象加鎖,進入同步代碼前要獲得當前類對象的鎖

修飾代碼塊,指定加鎖對象,對給定對象加鎖,進入同步代碼庫前要獲得給定對象的鎖。

在Java中,synchronized關(guān)鍵字是用來控制線程同步的,就是在多線程的環(huán)境下,控制synchronized代碼段不被多個線程同時執(zhí)行。synchronized既可以加在一段代碼上,也可以加在方法上。

關(guān)鍵是,不要認為給方法或者代碼段加上synchronized就萬事大吉,看下面一段代碼:

class Sync {

    public synchronized void test() {
        System.out.println("test開始..");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("test結(jié)束..");
    }
}

class MyThread extends Thread {

    public void run() {
        Sync sync = new Sync();
        sync.test();
    }
}

public class Main {

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            Thread thread = new MyThread();
            thread.start();
        }
    }
}

運行結(jié)果: test開始.. test開始.. test開始.. test結(jié)束.. test結(jié)束.. test結(jié)束..

可以看出來,上面的程序起了三個線程,同時運行Sync類中的test()方法,雖然test()方法加上了synchronized,但是還是同時運行起來,貌似synchronized沒起作用。

將test()方法上的synchronized去掉,在方法內(nèi)部加上synchronized(this):

public void test() {
    synchronized(this){
        System.out.println("test開始..");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("test結(jié)束..");
    }
}

運行結(jié)果: test開始.. test開始.. test開始.. test結(jié)束.. test結(jié)束.. test結(jié)束..

一切還是這么平靜,沒有看到synchronized起到作用。

實際上,synchronized(this)以及非static的synchronized方法(至于static synchronized方法請往下看),只能防止多個線程同時執(zhí)行同一個對象的同步代碼段。

回到本文的題目上:synchronized鎖住的是代碼還是對象。答案是:synchronized鎖住的是括號里的對象,而不是代碼。對于非static的synchronized方法,鎖的就是對象本身也就是this。

當synchronized鎖住一個對象后,別的線程如果也想拿到這個對象的鎖,就必須等待這個線程執(zhí)行完成釋放鎖,才能再次給對象加鎖,這樣才達到線程同步的目的。即使兩個不同的代碼段,都要鎖同一個對象,那么這兩個代碼段也不能在多線程環(huán)境下同時運行。

所以我們在用synchronized關(guān)鍵字的時候,能縮小代碼段的范圍就盡量縮小,能在代碼段上加同步就不要再整個方法上加同步。這叫減小鎖的粒度,使代碼更大程度的并發(fā)。原因是基于以上的思想,鎖的代碼段太長了,別的線程是不是要等很久,等的花兒都謝了。當然這段是題外話,與本文核心思想并無太大關(guān)聯(lián)。

再看上面的代碼,每個線程中都new了一個Sync類的對象,也就是產(chǎn)生了三個Sync對象,由于不是同一個對象,所以可以多線程同時運行synchronized方法或代碼段。

為了驗證上述的觀點,修改一下代碼,讓三個線程使用同一個Sync的對象。

class MyThread extends Thread {

    private Sync sync;

    public MyThread(Sync sync) {
        this.sync = sync;
    }

    public void run() {
        sync.test();
    }
}

public class Main {

    public static void main(String[] args) {
        Sync sync = new Sync();
        for (int i = 0; i < 3; i++) {
            Thread thread = new MyThread(sync);
            thread.start();
        }
    }
}

運行結(jié)果: test開始.. test結(jié)束.. test開始.. test結(jié)束.. test開始.. test結(jié)束..

可以看到,此時的synchronized就起了作用。

那么,如果真的想鎖住這段代碼,要怎么做?也就是,如果還是最開始的那段代碼,每個線程new一個Sync對象,怎么才能讓test方法不會被多線程執(zhí)行。

解決也很簡單,只要鎖住同一個對象不就行了。例如,synchronized后的括號中鎖同一個固定對象,這樣就行了。這樣是沒問題,但是,比較多的做法是讓synchronized鎖這個類對應的Class對象。

class Sync {

    public void test() {
        synchronized (Sync.class) {
            System.out.println("test開始..");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("test結(jié)束..");
        }
    }
}

class MyThread extends Thread {

    public void run() {
        Sync sync = new Sync();
        sync.test();
    }
}

public class Main {

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            Thread thread = new MyThread();
            thread.start();
        }
    }
}

運行結(jié)果: test開始.. test結(jié)束.. test開始.. test結(jié)束.. test開始.. test結(jié)束..

上面代碼用synchronized(Sync.class)實現(xiàn)了全局鎖的效果。

static synchronized方法,static方法可以直接類名加方法名調(diào)用,方法中無法使用this,所以它鎖的不是this,而是類的Class對象,所以,static synchronized方法也相當于全局鎖,相當于鎖住了代碼段。

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

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