自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Android進階之徹底理解Synchronized關(guān)鍵字

移動開發(fā) Android
synchronized是Java中的一個關(guān)鍵字,在多線程共同操作共享資源的情況下,可以保證在同一時刻只有一個線程可以對共享資源進行操作,從而實現(xiàn)共享資源的線程安全。

[[417605]]

本文轉(zhuǎn)載自微信公眾號「Android開發(fā)編程」,作者Android開發(fā)編程。轉(zhuǎn)載本文請聯(lián)系A(chǔ)ndroid開發(fā)編程公眾號。

一、Synchronized詳解

synchronized是Java中的一個關(guān)鍵字,在多線程共同操作共享資源的情況下,可以保證在同一時刻只有一個線程可以對共享資源進行操作,從而實現(xiàn)共享資源的線程安全。

二、Synchronized的特性

  1. 原子性。synchronized可以確保多線程下對共享資源的互斥訪問,被synchronized作用的代碼可以實現(xiàn)原子性。
  2. 可見性。synchronized保證對共享資源的修改能夠及時被看見。在Java內(nèi)存模型中,對一個共享變量操作后進行釋放鎖即進行unlock操作前,必須將修改同步到主內(nèi)存中。如果對一個共享資源進行加鎖即lock操作之前,必須將工作內(nèi)存中共享變量的值清空(因為每一個線程獲取的共享變量都是主存中共享變量的一個副本,如果不進行清空,就會發(fā)生數(shù)據(jù)不一致,即當(dāng)前線程中的共享變量與主存中的共享變量不一致),在使用此共享變量時,就需要從主存中重新加載此共享變量以獲得該共享變量最新的值。
  3. 有序性。synchronized可以有效解決重排序問題,即一個unlock解鎖操作必定先行發(fā)生于后面線程對同一個鎖的lock操作,這樣就會保證主內(nèi)存值的共享變量永遠是最新的。

三、Synchronized的使用

在應(yīng)用Sychronized關(guān)鍵字時需要把握如下注意點:

一把鎖只能同時被一個線程獲取,沒有獲得鎖的線程只能等待;

每個實例都對應(yīng)有自己的一把鎖(this),不同實例之間互不影響;例外:鎖對象是*.class以及synchronized修飾的是static方法的時候,所有對象公用同一把鎖;

synchronized修飾的方法,無論方法正常執(zhí)行完畢還是拋出異常,都會釋放鎖。

對象鎖

包括方法鎖(默認鎖對象為this,當(dāng)前實例對象)和同步代碼塊鎖(自己指定鎖對象鎖)

代碼塊形式:手動指定鎖定對象,也可是是this,也可以是自定義的鎖

  1. public class SynchronizedObjectLock implements Runnable { static SynchronizedObjectLock instence = new SynchronizedObjectLock(); // 創(chuàng)建2把鎖 Object block1 = new Object(); Object block2 = new Object(); @Override public void run() { // 這個代碼塊使用的是第一把鎖,當(dāng)他釋放后,后面的代碼塊由于使用的是第二把鎖,因此可以馬上執(zhí)行 synchronized (block1) { System.out.println("block1鎖,我是線程" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("block1鎖,"+Thread.currentThread().getName() + "結(jié)束"); } synchronized (block2) { System.out.println("block2鎖,我是線程" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("block2鎖,"+Thread.currentThread().getName() + "結(jié)束"); } } public static void main(String[] args) { Thread t1 = new Thread(instence); Thread t2 = new Thread(instence); t1.start(); t2.start(); } } 復(fù)制代碼 

輸出結(jié)果:

  1. block1鎖,我是線程Thread-0 block1鎖,Thread-0結(jié)束 block2鎖,我是線程Thread-0  // 可以看到當(dāng)?shù)谝粋€線程在執(zhí)行完第一段同步代碼塊之后,第二個同步代碼塊可以馬上得到執(zhí)行,因為他們使用的鎖不是同一把 block1鎖,我是線程Thread-1 block2鎖,Thread-0結(jié)束 block1鎖,Thread-1結(jié)束 block2鎖,我是線程Thread-1 block2鎖,Thread-1結(jié)束  

方法鎖形式:synchronized修飾普通方法,鎖對象默認為this

  1. public class SynchronizedObjectLock implements Runnable { static SynchronizedObjectLock instence = new SynchronizedObjectLock(); @Override public void run() { method(); } public synchronized void method() { System.out.println("我是線程" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "結(jié)束"); } public static void main(String[] args) { Thread t1 = new Thread(instence); Thread t2 = new Thread(instence); t1.start(); t2.start(); } }  

類鎖

包括方法鎖(默認鎖對象為this,當(dāng)前實例對象)和同步代碼塊鎖(自己指定鎖對象)

synchronize修飾靜態(tài)方法(類的class對象)

  1. public class SynchronizedObjectLock implements Runnable { static SynchronizedObjectLock instence1 = new SynchronizedObjectLock(); static SynchronizedObjectLock instence2 = new SynchronizedObjectLock(); @Override public void run() { method(); } // synchronized用在靜態(tài)方法上,默認的鎖就是當(dāng)前所在的Class類,所以無論是哪個線程訪問它,需要的鎖都只有一把 public static synchronized void method() { System.out.println("我是線程" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "結(jié)束"); } public static void main(String[] args) { Thread t1 = new Thread(instence1); Thread t2 = new Thread(instence2); t1.start(); t2.start(); } }  

輸出結(jié)果:

我是線程Thread-0 Thread-0結(jié)束 我是線程Thread-1 Thread-1結(jié)束 復(fù)制代碼

synchronized修改實例方法

  1. /** * synchronized修飾實例方法,當(dāng)線程拿到鎖,其他線程無法拿到該對象的鎖,那么其他線程就無法訪問該對象的其他同步方法 * 但是可以訪問該對象的其他非synchronized方法 * 鎖住的是類的實例對象 */ public class synchronizedDemo1 implements Runnable { //模擬一個共享數(shù)據(jù) private static int total=0; //同步方法,每個線程獲取到鎖之后,執(zhí)行5次累加操作 public synchronized void increase(){ for (int i = 1; i < 6; i++) { System.out.println(Thread.currentThread().getName()+"執(zhí)行累加操作..."+"第"+i+"次累加"); try { total=total+1; Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } //實例對象的另一個同步方法 public synchronized void declare(){ System.out.println(Thread.currentThread().getName()+"執(zhí)行total-1"); total--; System.out.println(Thread.currentThread().getName()+"執(zhí)行total-1完成"); } //普通實例方法 public void simpleMethod(){ System.out.println(Thread.currentThread().getName()+ " ----實例對象的普通方法---"); } @Override public void run() { //線程執(zhí)行體 System.out.println(Thread.currentThread().getName()+"準(zhǔn)備執(zhí)行累加,還沒獲取到鎖"); //執(zhí)行普通方法 simpleMethod(); //調(diào)用同步方法執(zhí)行累加操作 increase(); //執(zhí)行完increase同步方法后,會釋放掉鎖,然后線程1和線程2會再一次進行鎖的競爭,誰先競爭得到鎖,誰就先執(zhí)行declare同步方法 System.out.println(Thread.currentThread().getName()+"完成累加操作"); //調(diào)用實例對象的另一個同步方法 System.out.println(Thread.currentThread().getName()+"準(zhǔn)備執(zhí)行total-1"); declare(); } public static void main(String[] args) throws InterruptedException { synchronizedDemo1 syn = new synchronizedDemo1(); Thread thread1 = new Thread(syn,"線程1"); Thread thread2 = new Thread(syn,"線程2"); thread1.start(); thread2.start(); } }  

輸出結(jié)果:

線程1準(zhǔn)備執(zhí)行累加,還沒獲取到鎖 線程2準(zhǔn)備執(zhí)行累加,還沒獲取到鎖 線程2 ----實例對象的普通方法--- 線程2執(zhí)行累加操作...第1次累加 //線程2通過與線程1的競爭率先拿到了鎖,進入increase同步方法 線程2執(zhí)行累加操作...第2次累加 線程1 ----實例對象的普通方法--- //從這里可看出,在線程2訪問同步方法時,線程1是可以訪問非同步方法的,但是不可以訪問另外一個同步方法 線程2執(zhí)行累加操作...第3次累加 線程2執(zhí)行累加操作...第4次累加 線程2執(zhí)行累加操作...第5次累加 線程2完成累加操作 //線程2執(zhí)行累加后會釋放掉鎖 線程2準(zhǔn)備執(zhí)行total-1 線程1執(zhí)行累加操作...第1次累加 //然后線程1拿到鎖后進入increase同步方法執(zhí)行累加 線程1執(zhí)行累加操作...第2次累加 線程1執(zhí)行累加操作...第3次累加 線程1執(zhí)行累加操作...第4次累加 線程1執(zhí)行累加操作...第5次累加 線程1完成累加操作 //線程1完成累加操作也會釋放掉鎖,然后線程1和線程2會再進行一次鎖競爭 線程1準(zhǔn)備執(zhí)行total-1 線程2執(zhí)行total-1 //線程2通過競爭率先拿到鎖進入declear方法執(zhí)行total-1操作 線程2執(zhí)行total-1完成 線程1執(zhí)行total-1 線程1執(zhí)行total-1完成 復(fù)制代碼

四、Synchronized實現(xiàn)原理

加鎖和釋放鎖

synchronized同步是通過monitorenter和monitorexit等指令實現(xiàn)的,會讓對象在執(zhí)行,使其鎖計數(shù)器加1或者減1。

monitorenter指令:每一個對象在同一時間只與一個monitor(鎖)相關(guān)聯(lián),而一個monitor在同一時間只能被一個線程獲得,一個對象在嘗試獲得與這個對象相關(guān)聯(lián)的Monitor鎖的所有權(quán)的時候,會發(fā)生如下3種情況之一:

  • monitor計數(shù)器為0,意味著目前還沒有被獲得,那這個線程就會立刻獲得然后把鎖計數(shù)器+1,一旦+1,別的線程再想獲取,就需要等待
  • 如果這個monitor已經(jīng)拿到了這個鎖的所有權(quán),又重入了這把鎖,那鎖計數(shù)器就會累加,變成2,并且隨著重入的次數(shù),會一直累加
  • 若其他線程已經(jīng)持有了對象監(jiān)視器,則當(dāng)前線程進入阻塞狀態(tài),直到對象監(jiān)視器的進入數(shù)為0,重新嘗試獲取monitor的所有權(quán)。

monitorexit指令:釋放對于monitor的所有權(quán),釋放過程很簡單,就是講monitor的計數(shù)器減1,如果減完以后,計數(shù)器不是0,則代表剛才是重入進來的,當(dāng)前線程還繼續(xù)持有這把鎖的所有權(quán),如果計數(shù)器變成0,則代表當(dāng)前線程不再擁有該monitor的所有權(quán),即釋放鎖。

對象、對象監(jiān)視器、同步隊列以及執(zhí)行線程狀態(tài)之間的關(guān)系:

該圖可以看出,任意線程對Object的訪問,首先要獲得Object的監(jiān)視器,如果獲取失敗,該線程就進入同步狀態(tài),線程狀態(tài)變?yōu)锽LOCKED,當(dāng)Object的監(jiān)視器占有者釋放后,在同步隊列中得線程就會有機會重新獲取該監(jiān)視器。

可重入原理:加鎖次數(shù)計數(shù)器

從上圖中就可以看出來,執(zhí)行靜態(tài)同步方法的時候就只有一條monitorexit指令,并沒有monitorenter獲取鎖的指令。這就是鎖的重入性,即在同一鎖程中,線程不需要再次獲取同一把鎖。

Synchronized先天具有重入性。每個對象擁有一個計數(shù)器,當(dāng)線程獲取該對象鎖后,計數(shù)器就會加一,釋放鎖后就會將計數(shù)器減一。

保證可見性的原理:內(nèi)存模型和happens-before規(guī)則

Synchronized的happens-before規(guī)則,即監(jiān)視器鎖規(guī)則:對同一個監(jiān)視器的解鎖,happens-before于對該監(jiān)視器的加鎖。

public class MonitorDemo { private int a = 0; public synchronized void writer() { // 1 a++; // 2 } // 3 public synchronized void reader() { // 4 int i = a; // 5 } // 6 } 復(fù)制代碼

happens-before關(guān)系如圖所示:

在圖中每一個箭頭連接的兩個節(jié)點就代表之間的happens-before關(guān)系,黑色的是通過程序順序規(guī)則推導(dǎo)出來,紅色的為監(jiān)視器鎖規(guī)則推導(dǎo)而出:線程A釋放鎖happens-before線程B加鎖,藍色的則是通過程序順序規(guī)則和監(jiān)視器鎖規(guī)則推測出來happens-befor關(guān)系,通過傳遞性規(guī)則進一步推導(dǎo)的happens-before關(guān)系。

總結(jié)

  • synchronized同步語句塊的實現(xiàn)使?的是monitorenter和monitorexit指令,其中monitorenter指令指向同步代碼塊的開始位置, monitorexit指令則指明同步代碼塊的結(jié)束位置。
  • synchronized修飾的?法并沒有 monitorenter 指令和 monitorexit 指令,取得代之的確實是ACC_SYNCHRONIZED 標(biāo)識,該標(biāo)識指明了該?法是?個同步?法。

不過兩者的本質(zhì)都是對對象監(jiān)視器 monitor 的獲取。

使用Synchronized有哪些要注意的?

  • 鎖對象不能為空,因為鎖的信息都保存在對象頭里;
  • 作用域不宜過大,影響程序執(zhí)行的速度,控制范圍過大,編寫代碼也容易出錯;
  • 避免死鎖;
  • 在能選擇的情況下,既不要用Lock也不要用synchronized關(guān)鍵字,用java.util.concurrent包中的各種各樣的類,如果不用該包下的類,在滿足業(yè)務(wù)的情況下,可以使用synchronized關(guān)鍵字,因為代碼量少,避免出錯。

 

責(zé)任編輯:武曉燕 來源: Android開發(fā)編程
相關(guān)推薦

2019-12-20 15:19:41

Synchroinze線程安全

2024-03-15 15:12:27

關(guān)鍵字底層代碼

2022-01-26 00:03:00

關(guān)鍵字線程JVM

2021-03-10 15:59:39

JavaSynchronize并發(fā)編程

2017-05-27 20:59:30

Java多線程synchronize

2021-01-12 09:22:18

Synchronize線程開發(fā)技術(shù)

2024-11-20 15:55:57

線程Java開發(fā)

2022-06-29 08:05:25

Volatile關(guān)鍵字類型

2021-09-04 07:29:57

Android

2009-08-12 13:37:01

Java synchr

2011-07-14 23:14:42

C++static

2019-09-04 14:14:52

Java編程數(shù)據(jù)

2009-12-18 11:37:54

Ruby關(guān)鍵字yiel

2009-06-29 18:26:11

Java多線程Synchronize同步類

2024-03-15 11:52:03

C++關(guān)鍵字編程

2023-11-10 09:29:30

MySQLExplain

2024-12-26 00:28:59

C#base?關(guān)鍵字

2023-10-04 00:04:00

C++extern

2011-03-09 14:36:44

synchronizevolatile

2023-05-15 09:39:10

Java監(jiān)視器鎖
點贊
收藏

51CTO技術(shù)棧公眾號