日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發(fā)布時(shí)間:2025/3/21 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 并发编程-05线程安全性之原子性【锁之synchronized】 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

  • 線程安全性文章索引
  • 腦圖
  • 概述
  • 原子性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)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。