java synchronized關(guān)鍵字用法詳解(面試必考)

image.png

Synchronized 我們在使用中,常用的都是使用同步代碼塊,如下:

Synchronized (obj){

}

那么其實我們知道,synchronized是Java中的關(guān)鍵字,是一種同步鎖。它修飾的對象有以下幾種:

  1. 修飾一個代碼塊
  2. 修飾一個方法
  3. 修改一個靜態(tài)的方法
  4. 修改一個類,其作用的范圍是synchronized后面括號括起來的部分,作用主的對象是這個類的所有對象。

但是為什么我們在日常使用中很少用來直接修飾靜態(tài)方法、或者類呢?
那么帶著這樣的問題,我們先來看一看上面的這些用法所帶來的后果是什么!

synchronized修飾一個方法


package com.deem.thread.test;

public class Tick2 implements Runnable {
    private static int count;

    public Tick2() {
        count = 0;
    }

    @Override
    public synchronized void run() {
        for (int i = 0; i < 5; i++) {
            try {
                System.out.println(Thread.currentThread().getName() + ":" + (count++));
//                    Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    }
@Test
public void test(){
Tick2 t = new Tick2();

Thread t1 = new Thread(t,"tickThread1");
Thread t2 = new Thread(t,"tickThread2");

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

結(jié)果:
tickThread1:0
tickThread1:1
tickThread1:2
tickThread1:3
tickThread1:4
tickThread2:5
tickThread2:6
tickThread2:7
tickThread2:8
tickThread2:9

在這里我們可以看到先是tickThread1執(zhí)行完后,tickThread2才開始執(zhí)行的。
所以其實在這里我們來進行分析,synchronized 關(guān)鍵字在這里的用法獲取得到一個對象的鎖, 那么當tickThread1線程在執(zhí)行時,是已經(jīng)獲取得到了t這個對象的鎖,從而使得線程tickThread2被阻塞了,當tickThread1執(zhí)行完并釋放該對象鎖時,線程tickThread2才開始執(zhí)行。

關(guān)于修飾方法的寫法一般是下面兩種

寫法一
public synchronized void method()
{
   // todo
}
寫法二
public void method()
{
   synchronized(this) {
      // todo
   }
}

對于上面的是第一種寫法,第二種寫法其實是同步代碼塊的寫法,但在這里也是相當于修飾了方法,下面是第二種寫法,得出的結(jié)果與寫法一 一樣。

 @Override
    public void run() {
        synchronized (this) {
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + (count++));
//                    Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

synchronized修飾一個靜態(tài)方法


package com.deem.thread.test;

public class TickStatic implements Runnable {
    private static int count;

    public TickStatic() {
        count = 0;
    }

    public  void run() {
        method();
    }

    public synchronized static void method() {
        for (int i = 0; i < 5; i ++) {
            try {
                System.out.println(Thread.currentThread().getName() + ":" + (count++));
//                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}
 @Test
    public void test1(){
        TickStatic t_0 = new TickStatic();
        TickStatic t_1 = new TickStatic();

        Thread t1 = new Thread(t_0,"tickThread1");
        Thread t2 = new Thread(t_1,"tickThread2");

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

運行結(jié)果

tickThread1:0
tickThread1:1
tickThread1:2
tickThread1:3
tickThread1:4
tickThread2:5
tickThread2:6
tickThread2:7
tickThread2:8
tickThread2:9

其實這個時候我們會發(fā)現(xiàn)為什么明明是創(chuàng)建了兩個對象,線程還能保持同步呢?
這是因為run中調(diào)用了靜態(tài)方法method,而靜態(tài)方法是屬于類的,所以syncThread1和syncThread2相當于用了同一把鎖。這個鎖其實也可以叫做為類鎖,
在后續(xù)的章節(jié)中,將會詳細的講訴下類鎖和對象鎖之間的區(qū)別。

synchronized修飾一個類


用法如下

class ClassName {
   public void method() {
      synchronized(ClassName.class) {
         // todo
      }
   }
}
package com.deem.thread.test;

public class TickClass implements Runnable {
    private static int count;

    public TickClass() {
        count = 0;
    }

    public void run() {
        method();
    }

    public synchronized  void method() {
        synchronized (TickClass.class) {
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + (count++));
//                Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

    }

}

運行結(jié)果與TickStatic 執(zhí)行的結(jié)果是一樣的,因為synchronized作用于一個類T時,是給這個類T加鎖,T的所有對象用的是同一把鎖。

其實在上面的幾個用法當中,我們不難發(fā)現(xiàn),當兩個線程或者多個線程進行運行時,因為對象鎖或者類鎖被線程1占有而未得到釋放,使得其他的線程都被阻塞住了,而我們在實際的生產(chǎn)環(huán)境中,這樣必將導致資源耗盡,效率很低。

synchronized同步代碼塊


用法一:

synchronized(this) {
    //todo
      }

用法二

Object obj =new Object();
synchronized(obj) {
    //todo
      }
第一種用法如下:
package com.deem.thread.test;

public class TickCodeBlock implements Runnable {
    private static int count;

    public TickCodeBlock() {
        count = 0;
    }

    public void run() {
        method();
    }

    public void method() {
        synchronized (this) {
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + (count++));
//                Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

    }

}
 @Test
    public void test3(){
        TickCodeBlock t = new TickCodeBlock();

        Thread t1 = new Thread(t,"tickThread1");
        Thread t2 = new Thread(t,"tickThread2");

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

這時候,你會發(fā)現(xiàn),還是與上面相同的結(jié)果

tickThread1:0
tickThread1:1
tickThread1:2
tickThread1:3
tickThread1:4
tickThread2:5
tickThread2:6
tickThread2:7
tickThread2:8
tickThread2:9

但是呢 如果換成這種方式去運行呢

  @Test
    public void test3(){

        Thread t1 = new Thread(new TickCodeBlock(),"tickThread1");
        Thread t2 = new Thread(new TickCodeBlock(),"tickThread2");

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

結(jié)果

tickThread2:0
tickThread1:1
tickThread2:2
tickThread1:3
tickThread2:4
tickThread1:5
tickThread2:6
tickThread1:7
tickThread2:8
tickThread1:9

其實在這里我們可以知道,synchronized 用來給對象獲得對象鎖,當不同的對象時,對象鎖也是不一樣的,所以此時能夠保證兩個線程是互斥的,不會影響,從而也不會導致堵塞的情況

第二種用法如下:
package com.deem.thread.test;

public class TickCodeBlock implements Runnable {
    private static int count;
    private Object object =new Object();

    public TickCodeBlock() {
        count = 0;
    }

    public void run() {
        method2();
    }

  
    public void method2() {
        synchronized (object) {
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + (count++));
//                Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

    }

}

運行的結(jié)果是:

tickThread1:1
tickThread2:0
tickThread1:2
tickThread2:3
tickThread1:4
tickThread2:5
tickThread1:6
tickThread2:7
tickThread1:8
tickThread2:9

說明:零長度的byte數(shù)組對象創(chuàng)建起來將比任何對象都經(jīng)濟――查看編譯后的字節(jié)碼:生成零長度的byte[]對象只需3條操作碼,而Object lock = new Object()則需要7行操作碼。

總結(jié)

  1. 當synchronized用來修飾靜態(tài)方法或者類時,將會使得這個類的所有對象都是共享一把類鎖,導致線程阻塞,所以這種寫法一定要規(guī)避
  2. 無論synchronized關(guān)鍵字加在方法上還是對象上,如果它作用的對象是非靜態(tài)的,則它取得的鎖是對象;如果synchronized作用的對象是一個靜態(tài)方法或一個類,則它取得的鎖是對類,該類所有的對象同一把鎖。
  3. 每個對象只有一個鎖(lock)與之相關(guān)聯(lián),誰拿到這個鎖誰就可以運行它所控制的那段代碼。
  4. 實現(xiàn)同步是要很大的系統(tǒng)開銷作為代價的,甚至可能造成死鎖,所以盡量避免無謂的同步控制。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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