摘要:我叫運行結(jié)束三線程不安全導(dǎo)致請求丟失問題解決場景前面一的作用中的計數(shù)場景。我叫運行結(jié)束方法拋異常后,是否會釋放鎖拋出異常之后會釋放鎖,后面的線程會進(jìn)入同步方法。當(dāng)一個線程獲得了對應(yīng)的鎖的時候,其他線程只能等待我釋放之后才能獲取該鎖。
一、Synchronized的作用
作用:能夠保證在同一時刻最多只有一個線程執(zhí)行該代碼,以達(dá)到保證并發(fā)安全的效果
public class DisappearRequest implements Runnable{ static DisappearRequest dr = new DisappearRequest(); static int count = 0; public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(dr); Thread t2 = new Thread(dr); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("count="+count); } @Override public void run() { for (int i = 0; i < 10000; i++) { count++; } } } // 結(jié)果count小于20000(線程不安全)二、Synchronized的兩個用法
1. 對象鎖:包括同步代碼塊鎖(自己指定鎖對象)和方法鎖(默認(rèn)鎖對象為this當(dāng)前實例對象)
1.1 代碼塊形式:手動指定鎖對象
public class SynchronizedObjectCodeBlock implements Runnable { static SynchronizedObjectCodeBlock instance = new SynchronizedObjectCodeBlock(); public static void main(String[] args) { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); while(t1.isAlive() || t2.isAlive()){ } System.out.println("finished"); } @Override public void run() { // 兩個線程串行操作 synchronized(this){ System.out.println("我是對象鎖的代碼塊形式。我叫:" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "運行結(jié)束"); } } }
public class SynchronizedObjectCodeBlock implements Runnable { static SynchronizedObjectCodeBlock instance = new SynchronizedObjectCodeBlock(); Object lock1 = new Object(); Object lock2 = new Object(); public static void main(String[] args) { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); while(t1.isAlive() || t2.isAlive()){ } System.out.println("finished"); } @Override public void run() { synchronized(lock1){ System.out.println("我是對象鎖的代碼塊形式。我叫:" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "運行結(jié)束"); } synchronized(lock2){ System.out.println("我是對象鎖的代碼塊形式。我叫:" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "運行結(jié)束"); } } } // CountDownLatch、信號量解決線程同步問題。
1.2 方法鎖形式:synchronized修飾普通方法,鎖對象默認(rèn)為this
普通方法鎖
public class SynchronizedMethodLock implements Runnable{ static SynchronizedMethodLock instance = new SynchronizedMethodLock(); @Override public void run() { sync(); } // 普通方法鎖(不能是靜態(tài)方法。鎖對象默認(rèn)是this) public synchronized void sync(){ 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(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); while(t1.isAlive() || t2.isAlive()){ } System.out.println("finished"); } }
2. 類鎖:指synchronized修飾靜態(tài)方法或指定鎖為Class對象。Java類可能有很多個對象,但只有一個Class對象。
所謂的類鎖,不過是Class對象的鎖而已。
用法和效果:類鎖只能在同一時刻被一個對象擁有,其他對象會被阻塞
對象鎖如果不同的實例創(chuàng)建出來的,互相鎖是不受影響的,你可以運行我也可以運行,并行運行,但是類鎖只有一個可以運行。
2.1. synchronized加在static方法上。場景:如果需要在全局情況下同步該方法,而不是一個小范圍層面,則應(yīng)該用這種形式去做同步保護(hù)。
public class SynchronizedMethodLock implements Runnable{ // 創(chuàng)建兩個實例對象 static SynchronizedMethodLock instance1 = new SynchronizedMethodLock(); static SynchronizedMethodLock instance2 = new SynchronizedMethodLock(); @Override public void run() { sync(); } // 創(chuàng)建兩個實例對象,如果不是static方法的話,則并行操作,否則串行執(zhí)行。 public static synchronized void sync(){ System.out.println("我是類鎖的第一種形式:static形式。我叫:" + 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(instance1); Thread t2 = new Thread(instance2); t1.start(); t2.start(); while(t1.isAlive() || t2.isAlive()){ } System.out.println("finished"); } }
2.2. synchronized(*.class)代碼塊
public class SynchronizedMethodLock implements Runnable{ // 創(chuàng)建兩個實例對象 static SynchronizedMethodLock instance1 = new SynchronizedMethodLock(); static SynchronizedMethodLock instance2 = new SynchronizedMethodLock(); @Override public void run() { sync(); } // 創(chuàng)建兩個實例對象,如果不是static方法的話,則并行操作,否則串行執(zhí)行。 public void sync(){ // 如果是this的話則并行執(zhí)行,Class對象則串行執(zhí)行 synchronized(SynchronizedMethodLock.class){ System.out.println("我是類鎖的第二種形式:synchronized(*.class)形式。我叫:" + 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(instance1); Thread t2 = new Thread(instance2); t1.start(); t2.start(); while(t1.isAlive() || t2.isAlive()){ } System.out.println("finished"); } }三、線程不安全導(dǎo)致請求丟失問題解決
場景:前面【一、Synchronized的作用 】中的demo計數(shù)場景。
如下四種方式解決(結(jié)果均為20000):
3.1 @Override public synchronized void run() { for (int i = 0; i < 10000; i++) { count++; } }
3.2 @Override public void run() { synchronized (this){ for (int i = 0; i < 10000; i++) { count++; } } }
3.3 @Override public void run() { synchronized(DisappearRequest.class){ for (int i = 0; i < 10000; i++) { count++; } } }
3.4 @Override public void run() { add(); } public static synchronized void add(){ for (int i = 0; i < 10000; i++) { count++; } }四、多線程訪問同步方法的7中情況
4.1、 兩個線程同時訪問一個對象的同步方法
兩個線程爭搶的是同一把鎖,線程間相互等待,只能有一個線程持有該鎖
public static void main(String[] args) { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); while(t1.isAlive() || t2.isAlive()){ } System.out.println("finished"); }
4.2、 兩個線程訪問的是兩個對象的同步方法
創(chuàng)建兩個實例對象,如果不是static方法的話,則并行操作,否則串行執(zhí)行,這兩個實例真正采用的鎖對象不是同一個,所以不會被干擾。
// 創(chuàng)建兩個實例對象,如果不是static方法的話,則并行操作,否則串行執(zhí)行。 public void sync(){ // 如果是this的話則并行執(zhí)行,指向的是不同的實例對象,若為Class對象則串行執(zhí)行 synchronized(this){ // TO DO... } } public static void main(String[] args) { Thread t1 = new Thread(instance1); Thread t2 = new Thread(instance2); t1.start(); t2.start(); while(t1.isAlive() || t2.isAlive()){ } System.out.println("finished"); }
4.3、 兩個線程訪問的synchronized的靜態(tài)方法
如果兩個線程訪問的是同一個對象的同步方法則串行執(zhí)行,如果訪問的是不同對象的同步方法,若該方法是非靜態(tài)static方法則并行執(zhí)行,否則兩個線程訪問的鎖對象為同一把鎖,串行執(zhí)行。
public static synchronized void sync(){ // TO DO... } public static void main(String[] args) { Thread t1 = new Thread(instance1); Thread t2 = new Thread(instance2); t1.start(); t2.start(); while(t1.isAlive() || t2.isAlive()){ } System.out.println("finished"); }
4.4 同時訪問同步方法合肥同步方法
非同步方法不受到影響
4.5 訪問同一個對象的不同的普通同步方法
同一個對象,兩個同步方法拿到的this是一樣的,同一把鎖,所以串行執(zhí)行
4.6 同時訪問靜態(tài)synchronized和非靜態(tài)synchronized方法
兩個線程指定的鎖對象不是同一把鎖,所以鎖之間不沖突,并行執(zhí)行
// 創(chuàng)建兩個實例對象,如果不是static方法的話,則并行操作,否則串行執(zhí)行。 public static synchronized void sync(){ // 如果是this的話則并行執(zhí)行,Class對象則串行執(zhí)行 System.out.println("我是靜態(tài)方法:synchronized(*.class)形式。我叫:" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "運行結(jié)束"); } // 創(chuàng)建兩個實例對象,如果不是static方法的話,則并行操作,否則串行執(zhí)行。 public synchronized void sync1(){ // 如果是this的話則并行執(zhí)行,Class對象則串行執(zhí)行 System.out.println("我是非靜態(tài)方法:synchronized(*.class)形式。我叫:" + 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(instance1); Thread t2 = new Thread(instance1); t1.start(); t2.start(); while(t1.isAlive() || t2.isAlive()){ } System.out.println("finished"); }
4.7 方法拋異常后,是否會釋放鎖
拋出異常之后jvm會釋放鎖,后面的線程會進(jìn)入同步方法。
// 創(chuàng)建兩個實例對象 static SynchronizedMethodLock instance1 = new SynchronizedMethodLock(); static SynchronizedMethodLock instance2 = new SynchronizedMethodLock(); @Override public void run() { if(Thread.currentThread().getName().equals("Thread-0")){ sync(); }else{ sync1(); } } // 創(chuàng)建兩個實例對象,如果不是static方法的話,則并行操作,否則串行執(zhí)行。 public synchronized void sync(){ // 如果是this的話則并行執(zhí)行,Class對象則串行執(zhí)行 System.out.println("我是方法1:我叫:" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } throw new RuntimeException(); // System.out.println(Thread.currentThread().getName() + "運行結(jié)束"); } // 創(chuàng)建兩個實例對象,如果不是static方法的話,則并行操作,否則串行執(zhí)行。 public synchronized void sync1(){ // 如果是this的話則并行執(zhí)行,Class對象則串行執(zhí)行 System.out.println("我是方法2:我叫:" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "運行結(jié)束"); } public static void main(String[] args) { Thread t1 = new Thread(instance1); Thread t2 = new Thread(instance1); t1.start(); t2.start(); while(t1.isAlive() || t2.isAlive()){ } System.out.println("finished"); }五、synchronized的性質(zhì)
【5.1 可重入】:指的是同一線程的外層函數(shù)獲得鎖之后,內(nèi)層函數(shù)可以直接再次獲取該鎖
好處:避免死鎖,提升封裝性
比如:現(xiàn)在有兩個均被synchronized修飾的方法f1和f2,此時線程A執(zhí)行到了f1,并且獲得了這把鎖,由于方法二f2也是synchronized修飾(也就是說要執(zhí)行f2必須拿到f2的鎖),如果synchronized不具備可重入性,此時線程A只是拿到了f1的鎖,沒有拿到f2的鎖,所以線程A即想拿到f2的鎖又不釋放f1鎖,那么就會造成永遠(yuǎn)等待,即死鎖,所以synchronized是具有可重入性。
【可重入粒度如下】
5.1.1 證明同一個方法是可重入的(遞歸)5.1.2 證明可重入不要求是同一個方法
public synchronized void method1(){ System.out.println("我是method1"); method2(); } public synchronized void method2(){ System.out.println("我是method2"); }
5.1.3 證明同可重入不要求是同一個類中的
public class SyncSuperClass{ public synchronized void doSomething(){ System.out.println("我是父類方法"); } } class TestClass extends SyncSuperClass{ public synchronized void doSomething(){ System.out.println("我是子類方法"); super.doSomething(); } }
【5.2 不可中斷性】
一旦這個鎖已經(jīng)被別人獲得了,如果我還想獲得,我只能選擇等待或者阻塞,直到別的線程釋放這個鎖。如果別人永遠(yuǎn)不釋放鎖,那么我只能永遠(yuǎn)等下去。
Lock類:相比之下,Lock類,可以擁有中斷的能力,第一點,如果我覺得我等的時間太長了,有權(quán)中斷現(xiàn)在已經(jīng)獲取到鎖的線程的執(zhí)行;第二點,如果我覺得等待時間太長了不想等了,可以退出。
Lock lock = new ReentrantLock(); // 下面這兩種形式的鎖是等價的 public synchronized void method1(){ System.out.println("我是Synchronized形式的鎖"); } public void method2(){ lock.lock(); try{ System.out.println("我是Lock形式的鎖"); }finally{ lock.unlock(); } }六、synchronized的缺陷
【6.1 效率低】
鎖的釋放情況少,試圖獲得鎖時不能設(shè)定超時、不能中斷一個正在試圖獲得鎖的線程。
6.1.1、 當(dāng)一個線程獲得了對應(yīng)的sync鎖的時候,其他線程只能等待我釋放之后才能獲取該鎖。
6.1.2、 只有兩種情況才釋放鎖:1.執(zhí)行完了這段代碼,2.發(fā)生異常自動釋放鎖
6.1.3、 不能中斷,但是Lock是有中斷能力的
【6.2 不夠靈活(如:讀寫鎖比較靈活:讀的時候不加鎖,寫才加鎖)】
加鎖和釋放鎖的時機(jī)單一,每個鎖僅有單一的條件(某個對象),可能是不夠的
【6.3 無法知道是否成功獲取到鎖】
七、Lock鎖常用方法Lock lock = new ReentrantLock(); // 非公平鎖 // Lock lock = new ReentrantLock(true); // 公平鎖 // Lock lock = new ReentrantReadWriteLock(); lock.lock(); lock.unlock(); lock.tryLock(); // 獲取鎖 lock.tryLock(10, TimeUnit.SECONDS);八、常見面試題
8.1 使用synchroinzed注意點:鎖對象不能為空、作用域不宜過大、避免死鎖九、思考
注:一個對象作為鎖對象,這個對象必須是被new過的,或者是被其他方法創(chuàng)建好的,而不是一個空對象,因為鎖的信息保存在對象頭中的。8.2 如何選擇Lock和synchronized關(guān)鍵字
盡量使用concurrent包下的CountDownLatch或者atomic包,或者信號量
9.1 多個線程等待同一個synchronized鎖的時候,JVM如何選擇下一個獲取鎖的是哪個縣城?
9.2 synchronized使得同時只能有一個線程可以執(zhí)行,性能較差,有什么辦法可以提升性能?
9.3 我想更靈活的控制鎖的獲取和釋放(現(xiàn)在釋放鎖的時機(jī)都被規(guī)定死了),怎么辦?
9.4 什么是鎖的升級、降級?什么事JVM里的偏斜所、輕量級鎖。重量級鎖?
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/77565.html
摘要:基本使用同步代碼塊同步代碼塊延時秒,方便后面測試作用代碼塊時,方法中的,是指調(diào)用該方法的對象。那么這個時候使用關(guān)鍵字就需要注意了推薦使用同步代碼塊,同步的代碼塊中傳入外部定義的一個變量。 簡述 計算機(jī)單線程在執(zhí)行任務(wù)時,是嚴(yán)格按照程序的代碼邏輯,按照順序執(zhí)行的。因此單位時間內(nèi)能執(zhí)行的任務(wù)數(shù)量有限。為了能在相同的時間內(nèi)能執(zhí)行更多的任務(wù),就必須采用多線程的方式來執(zhí)行(注意:多線程模式無法減...
摘要:的關(guān)鍵字中的塊使用關(guān)鍵字進(jìn)行標(biāo)記。由于每個類只有一個類對象存在于中,因此全局同時只有一個線程能夠進(jìn)入到同一個類的靜態(tài)同步方法中。同步代碼塊使這種期望成為可能。注意同步代碼塊如何在括號中接受一個對象。相同的實例被傳入兩個不同的線程實例中。 Java的synchronized塊標(biāo)記一個方法或一個代碼塊為同步的。synchronized塊能用于防止出現(xiàn)競態(tài)條件。 Java的synchroni...
摘要:無論是互斥鎖,還是自旋鎖,在任何時刻,最多只能有一個保持者,也就說,在任何時刻最多只能有一個執(zhí)行單元獲得鎖。另外在中引入了自適應(yīng)的自旋鎖。和關(guān)鍵字的總結(jié)推薦一 該文已加入開源文檔:JavaGuide(一份涵蓋大部分Java程序員所需要掌握的核心知識)。地址:https://github.com/Snailclimb... 本文是對 synchronized 關(guān)鍵字使用、底層原理、JD...
摘要:關(guān)鍵字加到非靜態(tài)方法上持有的是對象鎖。線程和線程持有的鎖不一樣,所以和運行同步,但是和運行不同步。所以盡量不要使用而使用參考多線程編程核心技術(shù)并發(fā)編程的藝術(shù)如果你覺得博主的文章不錯,歡迎轉(zhuǎn)發(fā)點贊。 系列文章傳送門: Java多線程學(xué)習(xí)(一)Java多線程入門 Java多線程學(xué)習(xí)(二)synchronized關(guān)鍵字(1) java多線程學(xué)習(xí)(二)synchronized關(guān)鍵字(2) J...
摘要:轉(zhuǎn)載請備注地址多線程學(xué)習(xí)二將分為兩篇文章介紹同步方法另一篇介紹同步語句塊。如果兩個線程同時操作對象中的實例變量,則會出現(xiàn)非線程安全,解決辦法就是在方法前加上關(guān)鍵字即可。 轉(zhuǎn)載請備注地址: https://blog.csdn.net/qq_3433... Java多線程學(xué)習(xí)(二)將分為兩篇文章介紹synchronized同步方法另一篇介紹synchronized同步語句塊。系列文章傳送門...
摘要:使用可以禁止的指令重排,保證在多線程環(huán)境下也能正常運行。關(guān)鍵字底層原理總結(jié)關(guān)鍵字底層原理屬于層面。另外在中引入了自適應(yīng)的自旋鎖。自適應(yīng)的自旋鎖帶來的改進(jìn)就是自旋的時間不在固定了,而是和前一次同一個鎖上的自旋時間以及鎖的擁有者 【強(qiáng)烈推薦!非廣告!】阿里云雙11褥羊毛活動:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCc...
閱讀 707·2023-04-25 18:37
閱讀 2852·2021-10-12 10:12
閱讀 8526·2021-09-22 15:07
閱讀 616·2019-08-30 15:55
閱讀 3230·2019-08-30 15:44
閱讀 2252·2019-08-30 15:44
閱讀 1685·2019-08-30 13:03
閱讀 1616·2019-08-30 12:55