Java基礎(chǔ)之synchronized

synchronized是Java語言的關(guān)鍵字,可用來給對象方法或者代碼塊加鎖,當它鎖定一個方法或者一個代碼塊的時候,同一時刻最多只有一個線程執(zhí)行這段代碼。當兩個并發(fā)線程訪問同一個對象object中的這個加鎖同步代碼塊時,一個時間內(nèi)只能有一個線程得到執(zhí)行。另一個線程必須等待當前線程執(zhí)行完這個代碼塊以后才能執(zhí)行該代碼塊。然而,當一個線程訪問object的一個加鎖代碼塊時,另一個線程仍可以訪問該object中的非加鎖代碼塊。百度百科

看下面的代碼,使用synchronized鎖定方法的方式,鎖定了method1方法,期望同一時間只有一個線程可以訪問該方法。

public class SynchronizedTest implements Runnable{
    
    public synchronized void method1(String threadName) throws InterruptedException {
        System.out.println("線程" + threadName + ":開始!");
        Thread.sleep(3000);
        System.out.println("線程" + threadName + ":結(jié)束!");
    }

    @Override
    public void run() {
        try {
            method1(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
    }
    
    public static void main(String[] args) {
        for(int i = 0; i < 4; i++) {
            SynchronizedTest st = new SynchronizedTest();
            Thread t = new Thread(st);
            t.start();
        }
    }
}

運行結(jié)果

線程Thread-0:開始!
線程Thread-2:開始!
線程Thread-1:開始!
線程Thread-0:結(jié)束!
線程Thread-2:結(jié)束!
線程Thread-1:結(jié)束!

開始結(jié)束沒有成對兒輸出,這顯然不是我們想要的結(jié)果,下面換成synchronized鎖定代碼塊的方式。

public void method1(String threadName) throws InterruptedException {
    synchronized(this) {
        System.out.println("線程" + threadName + ":開始!");
        Thread.sleep(3000);
        System.out.println("線程" + threadName + ":結(jié)束!");
    }
}

運行結(jié)果

線程Thread-0:開始!
線程Thread-1:開始!
線程Thread-2:開始!
線程Thread-0:結(jié)束!
線程Thread-2:結(jié)束!
線程Thread-1:結(jié)束!

仍然不是我們期望的結(jié)果,為什么這兩種方式都沒能達到我們的預期呢?

原因是在創(chuàng)建多個線程時,創(chuàng)建了多個實例對象,也就是說上面兩種方式只有在同一個實例的時候才會生效。

修改main方法

    public static void main(String[] args) {
        SynchronizedTest st = new SynchronizedTest();
        for(int i = 1; i < 4; i++) {
            Thread t = new Thread(st);
            t.start();
        }
    }

運行結(jié)果

線程Thread-0方法1:開始!
線程Thread-0方法1:結(jié)束!
線程Thread-2方法1:開始!
線程Thread-2方法1:結(jié)束!
線程Thread-1方法1:開始!
線程Thread-1方法1:結(jié)束!

可以看到現(xiàn)在結(jié)果正確了,下面考慮一下不同實例的情況該如何實現(xiàn)呢?

public class SynchronizedTest implements Runnable{
    
    public static synchronized void method1(String threadName) throws InterruptedException {
        System.out.println("線程" + threadName + "開始!");
        Thread.sleep(1000);
        System.out.println("線程" + threadName + "結(jié)束!");
    }
    
    @Override
    public void run() {
        try {
            method1(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
    }
    
    public static void main(String[] args) {
        for(int i = 1; i < 4; i++) {
            SynchronizedTest st = new SynchronizedTest();
            Thread t = new Thread(st);
            t.start();
        }
    }
}

運行結(jié)果

線程Thread-0:開始!
線程Thread-0:結(jié)束!
線程Thread-2:開始!
線程Thread-2:結(jié)束!
線程Thread-1:開始!
線程Thread-1:結(jié)束!

可以看到,在同步方法上加上static關(guān)鍵字就對多實例線程起作用了,那么下面再看看代碼塊的方式如何實現(xiàn)?

    public void method1(String threadName) throws InterruptedException {
        synchronized(SynchronizedTest.class) {
            System.out.println("線程" + threadName + "開始!");
            Thread.sleep(1000);
            System.out.println("線程" + threadName + "結(jié)束!");
        }
    }

    public void method1(String threadName) throws InterruptedException {
        synchronized("obj") {
            System.out.println("線程" + threadName + "開始!");
            Thread.sleep(1000);
            System.out.println("線程" + threadName + "結(jié)束!");
        }
    }

以上兩種寫法都是正確的,就不再展示運行結(jié)果?,F(xiàn)在對上面五種寫法做一個總結(jié):


image.png

再思考一個問題,如果有兩個synchronized修飾的方法或代碼段,那么這兩個方法在同一時刻是否可以分別被不同的線程訪問?下面修改原來的方法,再新增一個method2

public class SynchronizedTest implements Runnable{

    public void method1(String threadName) throws InterruptedException {
        synchronized(SynchronizedTest .class) {
            System.out.println("線程" + threadName + "方法1:準備睡覺!");
            Thread.sleep(1000);
            System.out.println("線程" + threadName + "方法1:睡覺結(jié)束!");
        }
    }
    
    public void method2(String threadName) throws InterruptedException {
        synchronized(SynchronizedTest .class) {
            System.out.println("線程" + threadName + "方法2執(zhí)行!");
        }
    }

    @Override
    public void run() {
        try {
            method1(Thread.currentThread().getName());
            method2(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
    }
    
    public static void main(String[] args) {
        for(int i = 1; i < 4; i++) {
            SynchronizedTest st = new SynchronizedTest();
            Thread t = new Thread(st);
            t.start();
        }
    }

}

運行結(jié)果

線程Thread-0方法1:準備睡覺!
線程Thread-0方法1:睡覺結(jié)束!
線程Thread-2方法1:準備睡覺!
線程Thread-2方法1:睡覺結(jié)束!
線程Thread-1方法1:準備睡覺!
線程Thread-1方法1:睡覺結(jié)束!
線程Thread-2方法2執(zhí)行!
線程Thread-0方法2執(zhí)行!
線程Thread-1方法2執(zhí)行!

在方法1睡覺的過程中,沒有方法2的輸出,這說明在線程鎖定方法1的時候方法2同樣是被鎖定的,那么反之,非同步方法或代碼塊在其他線程沒有獲取鎖的情況下還是可以訪問的。下面去掉方法2的synchronized鎖:

    public void method2(String threadName) throws InterruptedException {
        System.out.println("線程" + threadName + "方法2執(zhí)行!");
    }

運行結(jié)果

線程Thread-0方法1:準備睡覺!
線程Thread-0方法1:睡覺結(jié)束!
線程Thread-0方法2執(zhí)行!
線程Thread-1方法1:準備睡覺!
線程Thread-1方法1:睡覺結(jié)束!
線程Thread-2方法1:準備睡覺!
線程Thread-1方法2執(zhí)行!
線程Thread-2方法1:睡覺結(jié)束!
線程Thread-2方法2執(zhí)行!

  • 彩蛋

看看下面兩種寫法是否效果一樣?

String obj = "obj";
    public void method1(String threadName) throws InterruptedException {
        synchronized(obj) {
            System.out.println("線程" + threadName + "開始!");
            Thread.sleep(1000);
            System.out.println("線程" + threadName + "結(jié)束!");
        }
    }
String obj = new String("obj");
    public void method1(String threadName) throws InterruptedException {
        synchronized(obj) {
            System.out.println("線程" + threadName + "開始!");
            Thread.sleep(1000);
            System.out.println("線程" + threadName + "結(jié)束!");
        }
    }

上面驗證過第一種寫法是正確的,貼一下第二個的運行結(jié)果

線程Thread-0開始!
線程Thread-2開始!
線程Thread-1開始!
線程Thread-0結(jié)束!
線程Thread-2結(jié)束!
線程Thread-1結(jié)束!

區(qū)別就在于String s = "s";會被存在常量區(qū),而String s = new String("s");是對象會放在堆中,這樣的話第二種寫法就變成鎖定的是實例對象了,故而結(jié)果是錯的。想要了解更多關(guān)于String的知識還請搜索引擎。

參考:
synchronized鎖住的是代碼還是對象
讓你徹底理解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ā)布平臺,僅提供信息存儲服務。

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

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