深入學習鎖--Synchronized各種使用方法

一、什么是synchronized

在Java當中synchronized通常是用來標記一個方法或者代碼塊。在Java當中被synchronized標記的代碼或者方法在同一個時刻只能夠有一個線程執行被synchronized修飾的方法或者代碼塊。因此被synchronized修飾的方法或者代碼塊不會出現數據競爭的情況,也就是說被synchronized修飾的代碼塊是并發安全的。synchronized是java內置關鍵字,是內置鎖,JVM中內置了,顆粒度比較大

二、synchronized的四種用法

2.1、修飾一個代碼塊

被修飾的代碼塊稱為同步語句塊,其作用的范圍是大括號{} 括起來的代碼,作用的對象是調用這個代碼塊的對象;

2.2、修飾一個方法

?被修飾的方法稱為同步方法,其作用的范圍是整個方法,作用的對象是調用這個方法的對象;

非靜態方法使用 synchronized 修飾的寫法,修飾實例方法時,鎖定的是當前實例對象

2.3、修飾一個靜態的方法

其作用的范圍是整個靜態方法,作用的對象是這個類的所有對象;

2.4、修飾一個類

其作用的范圍是synchronized后面括號括起來的部分,作用對象是這個類的所有對象

三、使用案例分析

3.1、修飾一個方法

class SyncDemo {private int count;public void add() {count++;}public static void main(String[] args) throws InterruptedException {SyncDemo syncDemo = new SyncDemo();Thread t1 = new Thread(()->{for (int i = 0; i <1000 ; i++) {syncDemo.add();}});Thread t2 = new Thread(()->{for (int i = 0; i <1000 ; i++) {syncDemo.add();}});t1.start();t2.start();t1.join();  //線程的阻塞方法,線程t1執行完畢,再執行main主線程打印語句t2.join();  //線程的阻塞方法,線程t2執行完畢,再執行main主線程打印語句System.out.println(syncDemo.count);}
}

由于add方法沒有使用synchronized修飾,線程t1和線程t2可能同時去執行add方法,那么就會導致最終count的結果小于20000,因為count++操作不具備原子性。?

將上述add方法被synchronized修飾

 public synchronized void add() {count++;}

由于add方法被synchronized修飾,因此每一個時刻只能有一個線程執行add方法,因此上面打印的結果是20000

總結:?

synchronized修飾的add方法一個時刻只能有一個線程執行的意思是對于一個SyncDemo類的對象來說一個時刻只能有一個線程進入。比如現在有兩個SyncDemo的對象s1s2,一個時刻只能有一個線程進行s1add方法一個時刻只能有一個線程進入s2add方法,但是同一個時刻可以有兩個不同的線程執行s1s2add方法,也就說s1add方法和s2add是沒有關系的一個線程進入s1add方法并不會阻止另外的線程進入s2add方法,也就是說synchronized在修飾一個非靜態方法的時候“鎖”住的只是一個實例對象并不會“鎖”住其它的對象。其實這也很容易理解,一個實例對象是一個獨立的個體別的對象不會影響他,他也不會影響別的對象。

3.2、修飾一個靜態的方法

class SyncDemo {private static int count;  //靜態變量public static synchronized void add() { //靜態方法count++;}public static void main(String[] args) throws InterruptedException {SyncDemo syncDemo = new SyncDemo();Thread t1 = new Thread(()->{for (int i = 0; i <1000 ; i++) {syncDemo.add();}});Thread t2 = new Thread(()->{for (int i = 0; i <1000 ; i++) {syncDemo.add();}});t1.start();t2.start();t1.join();  //線程的阻塞方法,線程t1執行完畢,再執行main主線程打印語句t2.join();  //線程的阻塞方法,線程t2執行完畢,再執行main主線程打印語句System.out.println(syncDemo.count);  //輸出結果為2000}
}

上面的代碼最終輸出的結果也是20000,但是與前一個程序不同的是。這里的add方法用static修飾的,在這種情況下真正只能有一個線程進入到add方法,因為用static修飾的add方法是靜態方法,靜態方法所有對象公共的方法,因此和前面的那種情況不同,不存在兩個不同的線程同一時刻執行不同實例對象的add方法。

你仔細想想如果能夠讓兩個不同的線程執行add代碼塊,那么count++的執行就不是原子的了。那為什么沒有用static修飾的代碼為什么可以呢?因為當沒有用static修飾時,每一個對象的count都是不同的,內存地址不一樣,因此在這種情況下count++這個操作仍然是原子的!

3.3、修飾一個代碼塊

class SyncDemo {private  int count;  //靜態變量public  void add() {System.out.println("進入了add方法");synchronized (this){count++;}}public  void minus() {System.out.println("進入了minus方法");synchronized (this){count--;}}public static void main(String[] args) throws InterruptedException {SyncDemo syncDemo = new SyncDemo();Thread t1 = new Thread(()->{for (int i = 0; i <10 ; i++) {syncDemo.add();}});Thread t2 = new Thread(()->{for (int i = 0; i <10 ; i++) {syncDemo.minus();}});t1.start();t2.start();t1.join();  //線程的阻塞方法,線程t1執行完畢,再執行main主線程打印語句t2.join();  //線程的阻塞方法,線程t2執行完畢,再執行main主線程打印語句System.out.println(syncDemo.count);  //輸出結果為0}
}

有時候我們并不需要用synchronized修飾一個方法因為這樣并發度就比較低了,一個方法一個時刻只能有一個線程在執行。因此我們可以選擇用synchronized去修飾代碼塊只讓某個代碼塊一個時刻只能有一個線程執行,除了這個代碼塊之外的代碼還是可以并行的。

比如上面的代碼當中addminus方法沒有使用synchronized進行修飾,因此一個時刻可以有多個線程執行這個兩個方法。在上面的synchronized代碼塊當中我們使用了this對象作為鎖對象,只有拿到這個鎖對象的線程才能夠進入代碼塊執行,而在同一個時刻只能有一個線程能夠獲得鎖對象。也就是說add函數和minus函數用synchronized修飾的兩個代碼塊同一個時刻只能有一個代碼塊的代碼能夠被一個線程執行,因此上面的結果同樣是0

這里說的鎖對象是this也就SyncDemo類的一個實例對象,因為它鎖住的是一個實例對象,因此當實例對象不一樣的時候他們之間是沒有關系的,也就是說不同實例用synchronized修飾的代碼塊是沒有關系的,他們之間是可以并發的。

3.4、修飾一個類

class SyncDemo {private  int count;  //靜態變量public  void add() {System.out.println("進入了add方法");synchronized (SyncDemo.class){count++;}}public  void minus() {System.out.println("進入了minus方法");synchronized (SyncDemo.class){count--;}}public static void main(String[] args) throws InterruptedException {SyncDemo syncDemo = new SyncDemo();Thread t1 = new Thread(()->{for (int i = 0; i <10 ; i++) {syncDemo.add();}});Thread t2 = new Thread(()->{for (int i = 0; i <10 ; i++) {syncDemo.minus();}});t1.start();t2.start();t1.join();  //線程的阻塞方法,線程t1執行完畢,再執行main主線程打印語句t2.join();  //線程的阻塞方法,線程t2執行完畢,再執行main主線程打印語句System.out.println(syncDemo.count);  //輸出結果為0}
}

上面的代碼是使用synchronized修飾類,鎖對象是SyncDemo.class,這個時候他不再是鎖住一個對象了,而是一個類了,這個時候的并發度就變小了,上一份代碼當鎖對象是SyncDemo的實例對象時并發度更大一些,因為當鎖對象是實例對象的時候只有實例對象內部是不能夠并發的,實例之間是可以并發的。但是當鎖對象是SyncDemo.class的時候,實例對象之間時不能夠并發的,因為這個時候的鎖對象是一個類。

四、Synchronized與可見性和重排序

4.1互斥性/排他性(非公平鎖)

synchronized(非公平鎖)會起到互斥效果,某個線程執?到某個對象的 synchronized 中時,其他線程如果也執?到同?個對象 synchronized 就會阻塞等待。

  • 進? synchronized 修飾的代碼塊, 相當于加鎖。
  • 退出 synchronized 修飾的代碼塊, 相當于解鎖。

PS:公平鎖 VS 非公平鎖
公平鎖:按資排輩,先到先得。需要“喚醒”這一步操作,會犧牲一定的性能。(線程發現鎖占用,嘗試獲取鎖一段時間后,進入休眠狀態,進入排隊隊列中等待。上一個線程釋放鎖后,會喚醒排隊等候的其他線程中最前面的線程從阻塞狀態又切換至運行狀態)
非公平鎖:來得早不如來得巧,不需要“喚醒”,性能高。(線程1發現鎖占用,嘗試獲取鎖一段時間后,進入休眠狀態。此時線程2來了,處于運行狀態,嘗試獲取鎖,此時剛好上一個線程釋放了鎖,那么線程2直接得到了鎖并去運行它的任務了。排隊等待的其他線程獲取鎖的順序不是按照訪問的順序先來先到的,而是由線程自己競爭,隨機獲取到鎖)Java里所有的鎖默認是非公平鎖。

4.2可見性

  • 當一個線程進入到synchronized同步代碼塊的時候,將會刷新所有對該線程的可見的變量,也就是說如果其他線程修改了某個變量,而且線程需要在Synchronized代碼塊當中使用,那就會重新刷新這個變量到內存當中,保證這個變量對于執行同步代碼塊的線程是可見的。

  • 當一個線程從同步代碼塊退出的時候,也會將線程的工作內存同步到內存當中,保證在同步代碼塊當中修改的變量對其他線程可見。

4.3可重入性(可重入性鎖)

synchronized 同步塊對同?條線程來說是可重?的,不會出現??把??鎖死的問題。

/*** synchronized 可重入性測試*/
public class ThreadSynchronized4 {public static void main(String[] args) {synchronized (ThreadSynchronized4.class) {System.out.println("當前主線程已經得到了鎖"); //當執行到此行代碼時,表示已經獲得鎖synchronized (ThreadSynchronized4.class) { //同一個線程獲取鎖兩次System.out.println("當前主線程再次得到了鎖"); //若兩行代碼都能打印,說明具備可重入性}}}
}

?

五、synchronized實現原理

(面試必問)synchronized是如何實現的?
①在Java代碼層面:

synchronized加鎖的對象里有一個的隱藏的對象頭,這個對象頭(可看作一個類)里有很多屬性,其中比較關注的兩個屬性是:是否加鎖的標識和擁有當前鎖的線程id。

每次進? synchronized 修飾的代碼塊時,會去對象頭中先判斷加鎖的標識,再判斷擁有當前鎖的線程id,從而決定當前線程能否往下繼續執行。

判斷加鎖標識為false->對象頭未加鎖,當前線程可以進入synchronized 修飾的代碼塊,并設置加鎖標識為true,設置擁有當前鎖的線程id為此線程id。
判斷加鎖標識為true->對象頭已加鎖,需進一步判斷擁有當前鎖的線程id是否為此線程id,若是,則繼續往下執行;否則,不能往下執行,需要等待鎖資源釋放后重新競爭再獲取鎖。
②在JVM層面和操作系統層面:

synchronized同步鎖是通過JVM內置的Monitor監視器實現的,而監視器又是依賴操作系統的互斥鎖Mutex實現的。↓
————————————————
原文鏈接:https://blog.csdn.net/WWXDwrn/article/details/129115774

六、synchronized歷史發展進程

  • 在JDK1.6之前(多使用Lock)synchronized使用很少,那時synchronized默認使用重量級鎖實現,所以性能較差。
  • 在JDK1.6時,synchronized做了優化。鎖升級流程如下:

1,無鎖:沒有線程訪問時默認是無鎖狀態,加了synchronized也是無鎖狀態。有線程訪問時才加鎖。更大程度上減少鎖帶來的程序上的開銷。
2,偏向鎖:當有一個線程訪問時會升級為偏向鎖。(在對象頭里存了這樣一把鎖,后面再來線程時會判斷,如果線程id和擁有鎖的線程id相同,會讓它進去,只偏向某一個線程,其他線程來了之后要繼續等)
3,輕量級鎖:當有多個線程訪問時會升級為輕量級鎖。
4,重量級鎖:當大量線程訪問同時競爭鎖資源的時候會升級為重量級鎖。

原文鏈接:https://baijiahao.baidu.com/s?id=1740505389877266267&wfr=spider&for=pc

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/211114.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/211114.shtml
英文地址,請注明出處:http://en.pswp.cn/news/211114.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Hazel引擎學習(十二)

我自己維護引擎的github地址在這里&#xff0c;里面加了不少注釋&#xff0c;有需要的可以看看 參考視頻鏈接在這里 Scene類重構 參考&#xff1a;《InsideUE4》GamePlay架構&#xff08;二&#xff09;Level和World 目前我的Scene類基本只是給entt的封裝&#xff0c;提供了…

工業4.0分類:數字化轉型的多維度

引言 工業4.0代表著制造業的數字化革命&#xff0c;它將制造過程帶入了數字時代。然而&#xff0c;工業4.0并不是一個單一的概念&#xff0c;而是一個多維度的范疇&#xff0c;包括不同的技術、應用領域、企業規模和實施方式。但在這一多維度的概念中&#xff0c;低代碼技術正…

如何優雅地使用Mybatis逆向工程生成類

文/朱季謙 1.環境&#xff1a;SpringBoot 2.在pom.xml文件里引入相關依賴&#xff1a; 1 <plugin>2 <groupId>org.mybatis.generator</groupId>3 <artifactId>mybatis-generator-maven-plugin</artifactId>4 <version>1.3.6<…

《三十》模塊化打包構建工具 Rollup

19的2小時06分鐘 Rollup 是一個 JavaScript 的模塊化打包工具&#xff0c;可以幫助編譯微小的代碼到龐大的復雜的代碼中&#xff08;例如一個庫或者一個應用程序&#xff09;。 Rollup 和 Webpack 的區別&#xff1a; Rollup 也是一個模塊化的打包工具&#xff0c;但是它主要…

排序:非遞歸的快排

目錄 非遞歸的快排&#xff1a; 代碼分析&#xff1a; 代碼演示&#xff1a; 非遞歸的快排&#xff1a; 眾所周知&#xff0c;遞歸變成非遞歸&#xff0c;而如果還想具有遞歸的功能&#xff0c;那么遞歸的那部分則需要變成循環來實現。 而再我們的排序中&#xff0c;我們可…

深入理解asyncio:異步編程的基礎用法

引言&#xff1a; 隨著計算機硬件的不斷發展&#xff0c;對于異步編程的需求也越來越強烈。Python中的asyncio模塊為開發者提供了一種強大而靈活的異步編程方式。本文將介紹asyncio的基礎用法&#xff0c;包括async/await/run語句的使用、多個協程的并發執行、以及在協程中進行…

Let和Var的區別

一&#xff1a;區別 Let不能重復聲明&#xff0c;且必須先聲明再調用&#xff1b; 但也可以只聲明不賦值&#xff0c;默認賦值undefined&#xff1b; 二&#xff1a;實例 let x 10; let x 20; // 這里將會報錯&#xff0c;因為 x 已經被聲明過了 console.log(y); let b …

Azure Machine Learning - 使用 Azure OpenAI 服務生成圖像

在瀏覽器/Python中使用 Azure OpenAI 生成圖像&#xff0c;圖像生成 API 根據文本提示創建圖像。 關注TechLead&#xff0c;分享AI全維度知識。作者擁有10年互聯網服務架構、AI產品研發經驗、團隊管理經驗&#xff0c;同濟本復旦碩&#xff0c;復旦機器人智能實驗室成員&#x…

【動態規劃】【廣度優先】LeetCode2258:逃離火災

作者推薦 本文涉及的基礎知識點 二分查找算法合集 動態規劃 二分查找 題目 給你一個下標從 0 開始大小為 m x n 的二維整數數組 grid &#xff0c;它表示一個網格圖。每個格子為下面 3 個值之一&#xff1a; 0 表示草地。 1 表示著火的格子。 2 表示一座墻&#xff0c;你跟…

pytorch:YOLOV1的pytorch實現

pytorch&#xff1a;YOLOV1的pytorch實現 注&#xff1a;本篇僅為學習記錄、學習筆記&#xff0c;請謹慎參考&#xff0c;如果有錯誤請評論指出。 參考&#xff1a; 動手學習深度學習pytorch版——從零開始實現YOLOv1 目標檢測模型YOLO-V1損失函數詳解 3.1 YOLO系列理論合集(Y…

Redis對象類型檢測與命令多態

一. 命令類型 Redis中操作鍵的命令可以分為兩類。 一種命令可以對任意類型的鍵執行&#xff0c;比如說DEL&#xff0c;EXPIRE&#xff0c;RENAME&#xff0c;TYPE&#xff0c;OBJECT命令等。 舉個例子&#xff1a; #字符串鍵 127.0.0.1:6379> set msg "hello world&…

第76講:MySQL數據庫中常用的命令行工具的基本使用

文章目錄 1.mysql客戶端命令工具2.mysqladmin管理數據庫的客戶端工具3.mysqlbinlog查看數據庫中的二進制日志4.mysqlshow統計數據庫中的信息5.mysqldump數據庫備份工具6.mysqllimport還原備份的數據7.source命令還原SQL類型的備份文件 MySQL數據庫提供了很多的命令行工具&#…

python 畫條形圖(柱狀圖)

目錄 前言 基礎介紹 月度開支的條形圖 前言 條形圖&#xff08;bar chart&#xff09;&#xff0c;也稱為柱狀圖&#xff0c;是一種以長方形的長度為變量的統計圖表&#xff0c;長方形的長度與它所對應的變量數值呈一定比例。 當使用 Python 畫條形圖時&#xff0c;通常會使…

python代碼:如何控制一個exe程序只能執行一次

import ctypes import sys def is_program_running(): # 創建互斥體 mutex_name "Global\\MonitorClientMutex" h_mutex ctypes.windll.kernel32.CreateMutexW(None, False, mutex_name) # 檢查互斥體是否已經存在 if ctypes.windll.kernel32.Get…

Centos7.9安裝谷歌【解決依賴問題】

安裝過程 mkdir /home/app cd /home/app wget https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpmyum install -y redhat-lsb-core-4.0-7.el6.centos.x86_64 yum install -y libX11-devel --nogpg yum install -y cmake gcc gcc-c gtk-devel gim…

vscode 編譯運行c++ 記錄

一、打開文件夾&#xff0c;新建或打開一個cpp文件 二、ctrl shift p 進入 c/c配置 進行 IntelliSense 配置。主要是選擇編譯器、 c標準&#xff0c; 設置頭文件路徑等&#xff0c;配置好后會生成 c_cpp_properties.json&#xff1b; 二、編譯運行&#xff1a; 1、選中ma…

zabbix 通過 odbc 監控 mssql

1、環境 操作系統&#xff1a;龍蜥os 8.0 zabbix&#xff1a;6.0 mssql&#xff1a;2012 2、安裝odbc 注意&#xff1a;需要在zabbix server 或者 zabbix proxy 安裝 odbc驅動程序 dnf -y install unixODBC unixODBC-devel3、安裝mssql驅動程序 注意&#xff1a;我最開始嘗試…

Tomcat管理功能使用

前言 Tomcat管理功能用于對Tomcat自身以及部署在Tomcat上的應用進行管理的web應用。在默認情況下是處于禁用狀態的。如果需要開啟這個功能&#xff0c;需要配置管理用戶&#xff0c;即配置tomcat-users.xml文件。 &#xff01;&#xff01;&#xff01;注意&#xff1a;測試功…

react 學習筆記 李立超老師 | (學習中~)

文章目錄 react學習筆記01入門概述React 基礎案例HelloWorld三個API介紹 JSXJSX 解構數組 創建react項目(手動)創建React項目(自動) | create-react-app事件處理React中的CSS樣式內聯樣式 | 內聯樣式中使用state (不建議使用)外部樣式表 | CSS Module React組件函數式組件和類組…

【數據結構和算法】反轉字符串中的單詞

其他系列文章導航 Java基礎合集數據結構與算法合集 設計模式合集 多線程合集 分布式合集 ES合集 文章目錄 其他系列文章導航 文章目錄 前言 一、題目描述 二、題解 2.1 方法一&#xff1a;雙指針 2.2 方法二&#xff1a;分割 倒序 三、代碼 3.1 方法一&#xff1a;雙…