日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

【Java】synchronized关键字笔记

發布時間:2023/12/20 java 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【Java】synchronized关键字笔记 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Java Synchronized 關鍵字

壹. Java并發編程存在的問題

1. 可見性問題

可見性問題是指一個線程不能立刻拿到另外一個線程對共享變量的修改的結果。

如:

package Note.concurrency;public class Demo07 {private static boolean s = true;public static void main(String[] args) {new Thread(() -> {while(s) {}}).start();try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {s = false;}).start();System.out.println(s);} }

運行之后,第一個線程一直沒有停止,說明第二個線程對s的修改沒有立刻被線程1拿到

2. 原子性問題

原子性問題是指一條Java語句有可能會被編譯成多條語句執行,多線程環境下修改同一個變量就會導致結果錯誤,如:

package Note.concurrency;import java.util.ArrayList; import java.util.List;public class Demo08 {private static int num = 0;public static void main(String[] args) {Runnable runnable = () -> {num ++;};List<Thread> list = new ArrayList<Thread>();for (int i = 0; i < 5; i++) {Thread thread = new Thread(runnable);thread.start();list.add(thread);}for (Thread t :list) {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(num);} }//~ 4

由于++不是原子性操作,通過反編譯,一個自增語句會被翻譯成四條語句執行:

private static void lambda$main$0();descriptor: ()Vflags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETICCode:stack=2, locals=0, args_size=00: getstatic #16 // Field num:I3: iconst_14: iadd5: putstatic #16 // Field num:I8: returnLineNumberTable:line 10: 0line 11: 8

3. 有序性問題

Java編譯時會優化代碼,這時如果兩個無關聯的語句Java可能會調整他的順序,導致有序性問題。

貳. 線程狀態

叁. Synchronized的用法

1. 修飾成員方法

在定義成員方法時添加關鍵字Synchronized可以保證同時只有一個線程執行此成員方法,線程進入成員方法時,需要先獲取鎖,方法執行完畢后,會自動釋放鎖,Synchronized修飾成員方法時,使用的鎖對象是this

2. 修飾靜態方法

因為靜態方法所有實例共享一份,所以相當于給類加鎖,鎖對象默認是當前類的字節碼文件,所以用Synchronized修飾的成員方法和靜態方法是可以并發運行的。

例:雙重檢驗鎖實現線程安全的單例模式:

package Note.concurrency;public class Singleton {private volatile static Singleton singleton;private Singleton(){}public static Singleton getSingleton() {if (singleton == null) {synchronized (Singleton.class) {if(singleton == null)singleton = new Singleton();}}return singleton;} }
  • singleton = new Singleton();不是原子操作,會被翻譯成下面四句之類,為保證線程安全,需要放在synchronized代碼塊中。
17: new #3 // class Note/concurrency/Singleton 20: dup 21: invokespecial #4 // Method "<init>":()V 24: putstatic #2 // Field
  • 為了避免不可見性問題,共享變量使用Volatile關鍵字修飾

3. 修飾代碼塊

修飾代碼塊時,要指定鎖對象,可以是任意的Object對象(盡量不要是String xxx , 因為String池有緩存),每個線程需要執行Synchronized代碼塊中的代碼時,要先獲取鎖對象,否則就會被阻塞, 代碼塊結束,鎖對象自動釋放。

肆. Synchronized的特性

1. 可重入

Synchronized是一個可重入鎖,意思是在獲得鎖后可以再次獲得該鎖而不會陷入死鎖

package Note.concurrency;public class ReentrantLockDemo {private int num = 0;final Object object = new Object();private void method1() {synchronized (object) {num ++;}}private void method2() {synchronized (object) {method1();}}public static void main(String[] args) {ReentrantLockDemo reentrantLockDemo = new ReentrantLockDemo();new Thread(reentrantLockDemo::method2).start();new Thread(reentrantLockDemo::method2).start();} }

2. 不可中斷

如果有A,B兩個線程競爭鎖,如果使用Synchronized,A獲得鎖后,如果不釋放,B將一直等下去,不能中斷。

package Note.concurrency;public class UninterruptibleDemo {private static final Object o = new Object();public static void main(String[] args) throws InterruptedException {// 線程1 拿到鎖后阻塞3snew Thread(() -> {synchronized (o) {System.out.println("線程1的同步代碼塊開始執行");try {Thread.sleep(6000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(" 線程1的同步代碼塊執行結束");}}).start();// 讓線程1先執行Thread.sleep(100);Thread t2 = new Thread(() -> {synchronized (o) {System.out.println("線程2的同步代碼塊開始執行");}});t2.start();// 主線程休眠3s后,鎖o 依然被線程1拿著,線程2處于BLOCKED狀態Thread.sleep(3000);System.out.println("線程2的狀態:" + t2.getState());// 嘗試中斷線程2,如果synchronized允許被中斷,那線程2此時的狀態應該會變為Terminated(死亡)狀態// 反之synchronized如果不可中斷,線程2的狀態會保持BLOCKED(阻塞)狀態t2.interrupt();System.out.println("線程2的狀態:" + t2.getState());} }//線程1的同步代碼塊開始執行 //線程2的狀態:BLOCKED //線程2的狀態:BLOCKED //線程1的同步代碼塊執行結束 //線程2的同步代碼塊開始執行

伍. Synchronized的原理

通過javap反匯編一下代碼

package Note.concurrency;public class Demo10 {public static synchronized void testMethod() {}public static void main(String[] args) {testMethod();synchronized(Demo10.class) {System.out.println("");}} }

可以看到,通過Synchronized修飾的代碼塊:

public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=3, args_size=10: ldc #2 // class Note/concurrency/Demo102: dup3: astore_14: monitorenter5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;8: ldc #4 // String10: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V13: aload_114: monitorexit15: goto 2318: astore_219: aload_120: monitorexit21: aload_222: athrow23: return Exception table:from to target type5 15 18 any18 21 18 any

對比普通語句,多了monitorenter和monitorexit,monitorenter是同步代碼塊開始的地方,monitorexit是同步代碼塊結束的地方,當線程運行到monitorenter后,會試圖獲取monitor對象,這個對象定義在JVM層(是一個C++的對象),每個Java對象都可以和一個monitor關聯,這個monitor對象保存在Java對象的對象頭中(這也是為什么任意的object對象都可以作為鎖的原因),這個monitor對象中有一個recursions屬性,用來保存被鎖的次數,第一次運行到monitorenter時,檢查要獲取的鎖關聯的monitor對象的recursions是不是0,如果是0,說明該鎖沒有被任何人獲取,就可以獲取鎖,把鎖的recursions加一,并將monitor對象的owner屬性設置為當前的Java對象。當執行monitorexit時, 會將recursions減一,當recursions減為0時,標志著當前占有鎖的線程釋放鎖。

第20行還有一個monitorexit,最下面的異常表顯示,如果5到15行發生異常,從18行開始執行,說明如果同步代碼塊中發生異常, 鎖會被自動釋放。

synchronized修飾方法的情況

public static synchronized void testMethod();descriptor: ()Vflags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZEDCode:stack=0, locals=0, args_size=00: returnLineNumberTable:line 5: 0

如果使用synchronized修飾方法,該方法會被添加上ACC_SYNCHRONIZED標記,添加了該標記的方法在執行時會隱式的調用monitorenter和monitorexit

陸. Synchronized和ReentrantLock的區別

  • Synchronized是一個關鍵字,依賴于JVM,而ReentrantLock是一個類,依賴于API;
  • Synchronized可以修飾方法,ReentrantLock只能修飾代碼塊;
  • synchronized可以自動釋放鎖,哪怕被它修飾的方法或代碼塊發生異常,它也可以把鎖釋放了,但ReentrantLock需要手動調用unlock()釋放鎖,與try...finally配合使用,避免死鎖;
  • ReentrantLock有比Synchronized更豐富的功能,如:
    • ReentrantLock可以做公平鎖,也可以做非公平鎖,但Synchronized就是非公平鎖。
    • ReentrantLock可以判斷對象有沒有拿到鎖。
    • ReentrantLock提供了一種能夠中斷等待鎖的線程的機制,通過lock.lockInterruptibly()等待鎖的線程可以放棄等待鎖去干別的事情。
    • Lock可以通過使用讀鎖提高性能。
  • 柒. Java 6 對Synchronized的優化

    1. 偏向鎖

    大多數情況下,鎖總是右同一線程多次獲得,不存在線程競爭,所以偏向鎖就是適用于這種情況,當有線程第一次獲取鎖時,JVM會把對象頭中的標志位設置為01,即偏向模式,并且將線程ID記錄下來,以后線程每次訪問同步代碼塊只需要判斷線程ID是不是記錄的偏向線程的ID,如果是就直接不進行同步了。但如果一旦發生線程競爭,偏向鎖就會升級為輕量級鎖。由于偏向鎖只能在全局安全點(所有線程全部停止)撤銷,所以在存在線程競爭的環境下使用偏向鎖會得不償失。

    在JDK5中偏向鎖默認是關閉的,而到了JDK6中偏向鎖已經默認開啟。但在應用程序啟動幾秒鐘之后才 激活,可以使用 -XX:BiasedLockingStartupDelay=0 參數關閉延遲,如果確定應用程序中所有鎖通常 情況下處于競爭狀態,可以通過 XX:-UseBiasedLocking=false 參數關閉偏向鎖。

    2. 輕量級鎖

    偏向鎖失效后會升級為輕量級鎖,輕量級鎖適用于線程交替執行同步代碼塊的情況下,它使用CAS操作代替重量級鎖使用操作系統互斥變量的操作,因此避免了程序頻繁在系統態和用戶態之間切換的開銷,但之所以使用輕量級鎖,是基于“對于絕大部分鎖,在整個同步周期內都是不存在競爭的”的經驗數據,如果有多個線程同時進入臨界區,那CAS操作的效率可能反而不如重量級鎖,如果存在線程競爭,輕量級鎖就會膨脹為重量級鎖。

    3. 自旋鎖

    一般情況下,同步代碼塊中的代碼執行時間都比較短,所以一時間獲取不到鎖,可能再重試一次就可以了,而不用升級為重量級鎖,自旋鎖就是基于這個原理,它允許獲取不到鎖的線程重復幾次嘗試獲取鎖,默認是10次,JDK 1.6 開始加入自適應自旋鎖,會根據之前自旋的情況動態確定自旋的次數。

    4. 重量級鎖

    經過自旋后還是獲取不到鎖,那就會升級為重量級鎖,也就是monitor

    5. 鎖消除

    鎖消除理解起來很簡單,它指的就是虛擬機即使編譯器在運行時,如果檢測到那些共享數據不可能存在競爭,那么就執行鎖消除。鎖消除可以節省毫無意義的請求鎖的時間。

    5. 鎖粗化

    JVM會探測到一連串細小的操作都使用同一個對象加鎖,將同步代碼塊的范圍放大,放 到這串操作的外面,這樣只需要加一次鎖即可。

    捌. 使用Synchronized時的優化

  • 減少Synchronized的范圍,讓同步代碼塊中的代碼執行時間盡可能短
  • 降低鎖粒度,將一個大鎖改為多個不同鎖對象的小鎖,如HashTable和ConcurrentHashMap
  • 讀寫分離,讀不加鎖,寫加鎖。
  • 拾. 參考

    https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/Multithread/synchronized.md

    https://www.bilibili.com/video/BV1aJ411V763

    總結

    以上是生活随笔為你收集整理的【Java】synchronized关键字笔记的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。