日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

并发编程-05线程安全性之原子性【锁之synchronized】

發布時間:2025/3/21 48 豆豆
生活随笔 收集整理的這篇文章主要介紹了 并发编程-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】的全部內容,希望文章能夠幫你解決所遇到的問題。

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