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