并发编程-05线程安全性之原子性【锁之synchronized】
文章目錄
- 線程安全性文章索引
- 腦圖
- 概述
- 原子性synchronized 修飾的4種對象
- 修飾代碼塊
- 作用范圍及作用對象
- Demo
- 多線程下 同一對象的調(diào)用
- 多線程下不同對象的調(diào)用
- 修飾方法
- 作用范圍及作用對象
- Demo
- 多線程下同一個(gè)對象的調(diào)用
- 多線程下不同對象的調(diào)用
- 修飾靜態(tài)方法
- 作用范圍及作用對象
- Demo
- 多線程同一個(gè)對象的調(diào)用
- 多線程下不同對象的調(diào)用
- 修飾類
- 作用范圍及作用對象
- Demo
- 多線程下同一對象的調(diào)用
- 多線程下不同對象的調(diào)用
- 使用Synchronized來保證線程安全
- 方法一
- 方法二
- 原子性的實(shí)現(xiàn)方式小結(jié)
- 代碼
線程安全性文章索引
并發(fā)編程-03線程安全性之原子性(Atomic包)及原理分析
并發(fā)編程-04線程安全性之原子性Atomic包的4種類型詳解
并發(fā)編程-05線程安全性之原子性【鎖之synchronized】
并發(fā)編程-06線程安全性之可見性 (synchronized + volatile)
并發(fā)編程-07線程安全性之有序性
腦圖
概述
舉個(gè)例子:
【多線程場景】假設(shè)有個(gè)變量a在主內(nèi)存中的初始值為1,線程A和線程B同時(shí)從主內(nèi)存中獲取到了a的值,線程A更新a+1,線程B也更新a+1,經(jīng)過線程AB更新之后可能a不等于3,而是等于2。因?yàn)锳和B線程在更新變量a的時(shí)候從主內(nèi)存中拿到的a都是1,而不是等A更新完刷新到主內(nèi)存后,線程B再從主內(nèi)存中取a的值去更新a,所以這就是線程不安全的更新操作.
解決辦法
- 使用鎖 1. 使用synchronized關(guān)鍵字synchronized會保證同一時(shí)刻只有一個(gè)線程去更新變量. 2、Lock接口 【篇幅原因先不討論lock,另開篇介紹】。
- 使用JDK1.5開始提供的java.util.concurrent.atomic包,見 并發(fā)編程-04線程安全性之原子性Atomic包詳解
先簡單說下synchronized和lock
-
synchronized 依賴jvm
-
lock 依賴特殊的cpu指令,代碼實(shí)現(xiàn),比如ReentranLock
這里我們重點(diǎn)來看下synchronized關(guān)鍵字是如何確保線程安全的原子性的。
原子性synchronized 修飾的4種對象
- 修飾代碼塊
- 修飾方法
- 修飾靜態(tài)方法
- 修飾類
修飾代碼塊
作用范圍及作用對象
被修飾的代碼被稱為同步語句塊,作用范圍為大括號括起來的代碼,作用于調(diào)用的對象, 如果是不同的對象,則互不影響
Demo
多線程下 同一對象的調(diào)用
package com.artisan.example.sync;import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;import lombok.extern.slf4j.Slf4j;@Slf4j public class SynchronizedDemo {public void test() {// 修飾代碼塊 ,誰調(diào)用該方法synchronized就對誰起作用 即作用于調(diào)用的對象 。 如果是不同的對象,則互不影響synchronized (this) {for (int i = 0; i < 10; i++) {log.info("修飾代碼塊 i = {} ",i);}}}public static void main(String[] args) {// 同一個(gè)調(diào)用對象SynchronizedDemo synchronizedDemo = new SynchronizedDemo();ExecutorService executorService = Executors.newCachedThreadPool();// 啟動兩個(gè)線程去 【使用同一個(gè)對象synchronizedDemo】調(diào)用test方法 for (int i = 0; i < 2; i++) {executorService.execute(() ->{synchronizedDemo.test();});}// 使用Thread 可以按照下面的方式寫 // for (int i = 0; i < 2; i++) { // new Thread(()-> { // synchronizedDemo.test2(); // }).start(); // }// 最后 關(guān)閉線程池executorService.shutdown();} }我們先思考下執(zhí)行的結(jié)果是什么樣子的?
上述代碼,我們通過線程池,通過循環(huán)開啟了2個(gè)線程去調(diào)用含有同步代碼塊的test方法 , 我們知道 使用synchronized關(guān)鍵字修飾的代碼塊作用的對象是調(diào)用的對象(同一個(gè)對象)。 因此這里的兩個(gè)線程都擁有同一個(gè)對象synchronizedDemo的引用,兩個(gè)線程,我們命名為線程A 線程B。 當(dāng)線程A調(diào)用到了test方法,因?yàn)橛衧ynchronized關(guān)鍵字的存在,線程B只能等待線程A執(zhí)行完。 因此A會輸出0~9,線程A執(zhí)行完之后,A釋放鎖,線程Bh獲取到鎖后,繼續(xù)執(zhí)行。
實(shí)際執(zhí)行結(jié)果:
符合我們分析和預(yù)測。
如果我們把 test 方法的synchronized關(guān)鍵字去掉會怎樣呢? 來看下
執(zhí)行結(jié)果
可知,synchronized關(guān)鍵字修飾的代碼塊,確保了同一調(diào)用對象在多線程的情況下的執(zhí)行順序。
多線程下不同對象的調(diào)用
為了更好地區(qū)分,我們給調(diào)用方法加個(gè)參數(shù)
package com.artisan.example.sync;import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;import lombok.extern.slf4j.Slf4j;@Slf4j public class SynchronizedDemo {public void test(String flag) {// 修飾代碼塊 ,誰調(diào)用該方法synchronized就對誰起作用 即作用于調(diào)用的對象 。 如果是不同的對象,則互不影響synchronized (this) {for (int i = 0; i < 10; i++) {log.info("{} 調(diào)用 修飾代碼塊 i = {} ",flag ,i);}}}public static void main(String[] args) {ExecutorService executorService = Executors.newCachedThreadPool();// 對象 synchronizedDemoSynchronizedDemo synchronizedDemo = new SynchronizedDemo();// 對象 synchronizedDemo2SynchronizedDemo synchronizedDemo2 = new SynchronizedDemo();// synchronizedDemo 調(diào)用 testexecutorService.execute(()->{synchronizedDemo.test("synchronizedDemo");});// synchronizedDemo2 調(diào)用 testexecutorService.execute(()->{synchronizedDemo2.test("synchronizedDemo2");});// 最后 關(guān)閉線程池executorService.shutdown();} }先來猜測下執(zhí)行結(jié)果呢?
兩個(gè)不同的對象,調(diào)用test方法,應(yīng)該是互不影響的,所以執(zhí)行順序是交替執(zhí)行的。
運(yùn)行結(jié)果:
修飾方法
被修飾的方法稱為同步方法,作用的范圍是整個(gè)方法,作用于調(diào)用的對象, 如果是不同的對象,則互不影響
作用范圍及作用對象
同 修飾代碼塊
Demo
增加個(gè)方法 test2
// 修飾方法 誰調(diào)用該方法synchronized就對誰起作用 即作用于調(diào)用的對象 。 如果是不同的對象,則互不影響public synchronized void test2() {// 修飾代碼塊 ,for (int i = 0; i < 10; i++) {log.info("調(diào)用 修飾代碼塊 i = {} ", i);}}多線程下同一個(gè)對象的調(diào)用
同 修飾代碼塊
結(jié)果:
多線程下不同對象的調(diào)用
同 修飾代碼塊
結(jié)果:
通過上面的測試結(jié)論可以知道 修飾代碼塊和修飾方法
如果一個(gè)方法內(nèi)部是一個(gè)完整的synchronized代碼塊,那么效果和synchronized修飾的方法效果是等同的 。
還有一點(diǎn)需要注意的是,如果父類的某個(gè)方法是synchronized修飾的,子類再調(diào)用該方法時(shí),是不包含synchronized. 因?yàn)閟ynchronized不屬于方法聲明的一部分。 如果子類想使用synchronized的話,需要在方法上顯示的聲明其方法為synchronized
修飾靜態(tài)方法
作用范圍及作用對象
整個(gè)靜態(tài)方法, 作用于所有對象
Demo
多線程同一個(gè)對象的調(diào)用
package com.artisan.example.sync;import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;import lombok.extern.slf4j.Slf4j;@Slf4j public class SynchronizedStaticMethodDemo {// 修飾靜態(tài)方法public synchronized static void test() {for (int i = 0; i < 10; i++) {log.info("調(diào)用 修飾方法 i = {} ", i);}}public static void main(String[] args) {ExecutorService executorService = Executors.newCachedThreadPool();for (int i = 0; i < 2; i++) {executorService.execute(() ->{test();});}// 最后 關(guān)閉線程池executorService.shutdown();} }結(jié)果:
多線程下不同對象的調(diào)用
package com.artisan.example.sync;import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;import lombok.extern.slf4j.Slf4j;@Slf4j public class SynchronizedStaticMethodDemo {// 修飾靜態(tài)方法public synchronized static void test() {for (int i = 0; i < 10; i++) {log.info("調(diào)用 修飾方法 i = {} ", i);}}public static void main(String[] args) {ExecutorService executorService = Executors.newCachedThreadPool();SynchronizedStaticMethodDemo demo1 = new SynchronizedStaticMethodDemo();SynchronizedStaticMethodDemo demo2 = new SynchronizedStaticMethodDemo();// demo1調(diào)用executorService.execute(() ->{// 其實(shí)直接調(diào)用test方法即可,這里僅僅是為了演示不同對象調(diào)用 靜態(tài)同步方法demo1.test();});// demo2調(diào)用executorService.execute(() ->{// 其實(shí)直接調(diào)用test方法即可,這里僅僅是為了演示不同對象調(diào)用 靜態(tài)同步方法demo2.test();});// 最后 關(guān)閉線程池executorService.shutdown();} }結(jié)果:
修飾類
作用范圍及作用對象
修飾范圍是synchronized括號括起來的部分,作用于所有對象
Demo
多線程下同一對象的調(diào)用
package com.artisan.example.sync;import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;import lombok.extern.slf4j.Slf4j;@Slf4j public class SynchronizedStaticClassDemo2 {// 修飾一個(gè)類public void test() {synchronized (SynchronizedStaticClassDemo2.class) {for (int i = 0; i < 10; i++) {log.info("調(diào)用 修飾方法 i = {} ", i);}}}public static void main(String[] args) {ExecutorService executorService = Executors.newCachedThreadPool();SynchronizedStaticClassDemo2 demo = new SynchronizedStaticClassDemo2();// demo調(diào)用executorService.execute(() ->{demo.test();});// demo調(diào)用executorService.execute(() ->{demo.test();});// 最后 關(guān)閉線程池executorService.shutdown();} }結(jié)果
多線程下不同對象的調(diào)用
package com.artisan.example.sync;import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;import lombok.extern.slf4j.Slf4j;@Slf4j public class SynchronizedStaticClassDemo2 {// 修飾一個(gè)類public void test() {synchronized (SynchronizedStaticClassDemo2.class) {for (int i = 0; i < 10; i++) {log.info("調(diào)用 修飾方法 i = {} ", i);}}}public static void main(String[] args) {ExecutorService executorService = Executors.newCachedThreadPool();SynchronizedStaticClassDemo2 demo1 = new SynchronizedStaticClassDemo2();SynchronizedStaticClassDemo2 demo2 = new SynchronizedStaticClassDemo2();// demo1調(diào)用executorService.execute(() ->{demo1.test();});// demo2調(diào)用executorService.execute(() ->{demo2.test();});// 最后 關(guān)閉線程池executorService.shutdown();} }結(jié)果
使用Synchronized來保證線程安全
先回顧下 線程不安全的寫法
方法一
下面用Synchronized來改造下
我們知道synchronized修飾靜態(tài)方法,作用的對象是所有對象 , 因此 僅需要將 靜態(tài)add方法 修改為同步靜態(tài)方法即可。
多次運(yùn)算
方法二
假設(shè) add方法不是靜態(tài)方法呢? 我們知道 當(dāng)synchronized修飾普通方法,只要是同一個(gè)對象,也能保證其原子性
假設(shè) add方法為普通方法
改造如下:
多次運(yùn)行,結(jié)果總是10000
原子性的實(shí)現(xiàn)方式小結(jié)
-
synchronized
不可中斷鎖,適合不激烈的競爭,可讀性較好 -
atomic包
競爭激烈時(shí)能維持常態(tài),比Lock性能好,但只能同步一個(gè)值 -
lock
可中斷鎖,多樣化同步,競爭激烈時(shí)能維持常態(tài)。后面針對lock單獨(dú)展開。
代碼
https://github.com/yangshangwei/ConcurrencyMaster
總結(jié)
以上是生活随笔為你收集整理的并发编程-05线程安全性之原子性【锁之synchronized】的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 并发编程-04线程安全性之原子性Atom
- 下一篇: 并发编程-06线程安全性之可见性 (sy