Synchronized關鍵字的使用區別

Synchronized關鍵字的使用區別

常用API

method 註釋
run() run()方法是我們創建線程時必須要實現的方法,但是實際上該方法只是一個普通方法,直接調用並沒有開啓線程的作用。
start() start()方法作用爲使該線程開始執行;Java虛擬機調用該線程的 run 方法。 但是該方法只能調用一次,如果線程已經啓動將會拋出IllegalThreadStateException異常。
yield() yield()方法讓出CPU並且不會釋放鎖,讓當前線程變爲可運行狀態,所以CPU下一次選擇的線程仍可能是當前線程
wait() wait()方法使得當前線程掛起,放棄CPU的同時也放棄同步資源(釋放鎖),讓其他等待這些資源的線程能繼續執行,只有當使用notify()/notifyAll()方法是纔會使得等待的線程被喚醒,使用此方法的前提是已經獲得鎖。
notify()/notifyAll() notify()/notifyAll()方法將喚醒當前鎖上的一個(全部)線程,需要注意的事一般都是使用的notifyAll()方法,因爲notify()方法的喚醒是隨機的,我們沒有辦法控制

同步

爲什麼會出現線程不安全?

現在操作系統中進程是作爲資源分配的基本單位,而線程是作爲調度的基本單位,一般而言,線程自己不擁有系統資源,但它可以訪問其隸屬進程的資源,即一個進程的代碼段、數據段及所擁有的系統資源,如已打開的文件、I/O設備等,可以供該進程中的所有線程所共享,一旦有多個線程在操作同樣的資源就可能造成線程安全的問題。
在我們熟悉的Java中存在着局部變量和類變量,其中局部變量是存放在棧幀中的,隨着方法調用而產生,方法結束就被釋放掉,而棧幀是獨屬於當前線程的,所以不會有線程安全的問題。而類變量是被存放在堆內存中,可以被所有線程共享,所以也會存在線程安全的問題。

synchronized

在Java中我們見得最多的同步的方法應該就是使用synchronized關鍵字了。實際上synchronized就是一個互斥鎖,當一個線程運行到使用了synchronized的代碼段時,首先檢查當前資源是否已經被其他線程所佔用,如果已經被佔用,那麼該線程則阻塞在這裏,直到擁有資源的線程釋放鎖,其他線程纔可以繼續申請資源。

實現簡單理解

synchronized的實現對於方法的修飾和代碼塊的修飾實現不太一樣。

代碼塊:

public static void test(){
    synchronized (SyncDemo.class){
    }
}

//編譯後的代碼
  public static void test();
    Code:
       0: ldc           #3                  //將一個常量加載到棧中這裏既是class com/learn/set/mutilthread/sync/SyncDemo
       2: dup               //複製棧頂元素(SyncDemo.class)
       3: astore_0          //將棧頂元素存儲到局部變量表
       4: monitorenter      //以字節碼對象(SyncDemo.class)爲鎖開始同步操作
       5: aload_0           //將局部變量表slot_0入棧(SyncDemo.class)
       6: monitorexit       //退出同步
       7: goto          15  //到這裏程序跳轉到return語句正常結束,下面代碼是異常路徑
      10: astore_1
      11: aload_0
      12: monitorexit
      13: aload_1
      14: athrow
      15: return

修飾方法:

   public synchronized void test1(){

   }
    
  //編譯後的代碼
  public synchronized void test1();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 13: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lcom/test/sync/SyncDemo;



代碼塊的修飾通過monitorenter和monitorexit指令完成。而方法的修飾則是通過一個標誌位ACC_SYNCHRONIZED完成。

synchronized的使用

在Java語言中,synchronized關鍵字可以用來修飾方法以及代碼塊:

修飾方法

    //修飾普通方法
    public synchronized void say(){
        
    }
    //修飾靜態方法
    public synchronized static void fun(){
        
    }

修飾代碼塊

    public void fun1(){
        //使用當前對象爲鎖
        synchronized (this){
            //statement
        }
    }

    public void fun2(){
        //使用當前類字節碼對象爲鎖
        synchronized (SyncDemo.class){
            //statement
        }
    }

synchronized在不同場景下的區別

實體類:

public class User {
    private static int age = 20;

    public synchronized void say(String user) throws InterruptedException {
//        synchronized (User.class){
        System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
        //當前線程休眠,判斷別的線程是否還能調用
        Thread.sleep(1000);
        System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
//        }
    }

    public synchronized void say1(String user) throws InterruptedException {
//        synchronized (User.class){
        System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
        Thread.sleep(1000);
        age = 15;
        System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
//        }
    }
}


測試類

public class SyncTest{
    private static User user1 = new User();
    private static User user2 = new User();

    private static class Sync1 extends Thread{
        @Override
        public void run() {
            try {
                user1.say("user1");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private static class Sync2 extends Thread{
        @Override
        public void run() {
            try {
                user2.say("user2");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Sync1 sync1 = new Sync1();
        Sync2 sync2 = new Sync2();
        sync1.start();
        sync2.start();
    }
}
運行結果:
第一次運行:
20:Thread-1:user2
20:Thread-0:user1
20:Thread-0:user1
20:Thread-1:user2
第二次運行:
20:Thread-1:user2
20:Thread-0:user1
20:Thread-1:user2
20:Thread-0:user1

運行結果表示在普通方法上加synchronized關鍵字實際上是鎖的當前對象,所以不同線程操作不同對象結果可能出現不一致。修改實體類User的say(…)方法爲靜態方法:

public synchronized void say(String user) throws InterruptedException {
        System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
        Thread.sleep(1000);
        System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
    }

運行結果始終按照順序來:

20:Thread-0:user1
20:Thread-0:user1
20:Thread-1:user2
20:Thread-1:user2

說明在靜態(類)方法上加synchronized關鍵字實際上是鎖的當前類的字節碼對象,因爲在JVM中任何類的字節碼對象都只有一個,所以只要對該字節碼對象加鎖那麼任何對該類的操作也都是同步的。

在最初類的基礎上修改類Sync2,使得兩個線程操作統一對象:

private static class Sync2 extends Thread{
        @Override
        public void run() {
            try {
                user1.say("user2");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

運行結果始終按照順序來:

20:Thread-0:user1
20:Thread-0:user1
20:Thread-1:user2
20:Thread-1:user2

同理可測試在使用synchronized修飾代碼塊的作用,可得結果使用this對象實際是鎖當前對象,與synchronized修飾普通方法類似,使用User.class字節碼對象實際是鎖User類的字節碼對象,與synchronized修飾靜態方法類似。需要說明的事鎖代碼塊實際上並不是必須使用當前類的this對象和字節碼對象,而可以是任意的對象。而實際效果和使用當前類的對象一致

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章