Java并發編程之synchronized關鍵字解析

前言

????????公司加班太狠了,都沒啥時間充電,這周終于結束了。這次整理了Java并發編程里面的synchronized關鍵字,又稱為隱式鎖,與JUC包中的Lock顯示鎖相對應;這個關鍵字從Java誕生開始就有,稱之為重量級鎖,自從JDK1.6之后官方對該關鍵字進行優化,引入了輕量級鎖和偏向鎖,于是就有了鎖升級的概念。

使用

在代碼中使用這個關鍵字總共有以下三種:

    private static Object object = new Object();private synchronized void function1() {//鎖住當前實例對象}private static synchronized void function2() {//鎖住當前class對象,可以認為是鎖住當前Class文件}public static void main( String[] args ) {synchronized (object) {//鎖住object對象}}

1:普通方法同步;

2:靜態方法同步;

3:同步代碼塊括號中的對象;

使用synchronized關鍵字進行同步,則鎖是儲存于Java對象頭里面的Mark Word。

Java對象頭里面的Mark Word里面主要是存儲對象的hashCode、分代年齡(用于判斷為老年代還是年輕代,在垃圾回收器里面用得到)以及鎖標記位。

鎖的升級

在JDK1.6之后,引入了引入了偏向鎖和輕量級鎖的狀態,目的是為了提升鎖的釋放和獲取效率,減少性能的開銷,所以synchronized就有四種級別鎖的狀態,級別從低到高分別是無鎖狀態、偏向鎖狀態、輕量級鎖以及重量級鎖狀態;四種狀態實質上是對象頭儲存的鎖標記位不一致,使用CAS更改對象頭的標記位進行鎖狀態位;并且在記錄鎖的標記位的同時,也會在Mark Word里面記錄鎖線程的ID

無鎖

既在對象頭的Mark Word沒有標記鎖狀態的時候就是無鎖狀態

偏向鎖

單個線程進行訪問或調用帶synchronized的同步代碼塊或方法時,會在先判斷對象頭里面鎖標志位是否有線程ID,如果沒有線程ID的話,將當前線程ID寫入進去,并且也會在棧幀中的鎖記錄里面進行記錄。此時,鎖的狀態位為偏向鎖,通俗來說可以說是只要單線程訪問同步代碼塊,從無鎖狀態就會便成為偏向鎖狀態;如果在對象頭里面存在線程ID的話,如果當前線程ID是與對象頭里面記錄的線程ID的話,那么就可以直接訪問,不需要使用CAS去進行競爭鎖了;不一致的話,那么就會使用CAS進行競爭鎖。

當其他線程開始競爭偏向鎖的時候,那么持有偏向鎖的線程就去會釋放偏向鎖,供其他線程使用;這時候就出現一種偏向鎖的撤銷概念

偏向鎖的撤銷

偏向鎖的撤銷就是,會先暫停持有偏向鎖的線程,然后去對象頭里面判斷記錄線程ID的線程是否還在處于活動狀態,如果處于非活動狀態,那么就會將Mark Word里面的鎖標志位設置為無鎖狀態;如果處于活動狀態的話,會先遍歷偏向對象的鎖記錄、棧里面鎖記錄以及對象頭里面Mark Word里面的鎖標記位,并且將對象頭中鎖標志位設置為無鎖狀態或者升級成輕量鎖狀態的,然后喚醒持有偏向鎖的的線程,繼續執行;

輕量級鎖

加鎖

當執行同步代碼塊升級為輕量級鎖的時候,會在棧幀中創建一塊用于儲存鎖記錄的空間,并且將對象頭里面Mark Word復制到記錄中(Displaced Mark Word),線程開始會使用CAS將Mark Word替換為指向鎖記錄的指針,如果獲取成功,那么當前線程獲取鎖,如果失敗,采用自旋來獲取鎖,如果自旋獲取鎖失敗,那么會膨脹為重量級鎖。

解鎖

輕量級鎖解鎖會使用CAS將Displaced Mark Word替換回到對象頭中,如果成功了,表示沒有鎖競爭;如果失敗了表示有鎖在競爭,那么就會膨脹成重量級鎖,那么在自旋的獲取鎖的線程就會進行線程阻塞;

由于自旋會大量消耗CPU資源,所以一旦升級成為了重量級鎖之后,那么就不會進行降級了。

重量級鎖

這個就是線程阻塞了,基本上可以和Lock表現一致了,一旦有線程獲取鎖,其他獲取鎖的線程將會阻塞,釋放鎖之后將會喚醒阻塞線程去競爭鎖

優點缺點使用場景
偏向鎖加鎖和解鎖不需要額外的資源消耗,性能快如果線程之間存在鎖競爭,那么會出現鎖撤銷,暫停線程,比較消耗資源適用于單線程使用場景
輕量級鎖線程不會阻塞,提高響應程度自旋消耗CPU性能,容易升級為重量級鎖適用于同步代碼塊執行非常快的
重量級鎖線程不會自旋,避免過多消耗CPU資源線程阻塞,響應時間緩慢提高吞吐量,同步代碼塊執行較長

等待/通知機制

這個之前是有篇講過線程之間的共享協作:線程協作?這個里面提到過如何使用

現在看看底層是如何運行的執行,我們先看一段代碼

public class SynchronziedDemo {public static Object object = new Object();public static void main(String[] args) {synchronized (object) {}m();}public static synchronized void m() {}
}

將這段代碼編譯后使用javap -v命令進行反編譯

會得到class文件的編譯后的c代碼:

同步代碼塊使用的事monitorenter(獲取鎖)和monitorexit(釋放鎖)指令,同步放上是使用ACC_SYNCRONIZED來完成的。

無論是哪個本質上其實是個monitor監視器進行完成的,每個對象都會有一個監視器,線程需先獲取到monitor監視器才能訪問同步代碼塊或者方法,而沒有獲取到的線程就會進入自旋,升級為重量級鎖,然后會進入到阻塞狀態,這時候會有一個同步隊列(SynchronizedQueue),阻塞的線程會加入到這個隊列里面,等待獲取到監視器的線程調用monitorexit指定后,會喚醒同步隊列里面的等待線程。

等待/通知機制里面對了一個WaitQueue即等待隊列,案例:


public class WaitNotify {public static Object object = new Object();public static void main(String[] args) {new Thread(new WaitClass()).start();new Thread(new NotifyClass()).start();}static class NotifyClass implements Runnable {@Overridepublic void run() {synchronized (object) {try {TimeUnit.SECONDS.sleep(2L);System.out.println(Thread.currentThread() + "notify start...");object.notifyAll();System.out.println(Thread.currentThread() + "notify end...");} catch (InterruptedException e) {e.printStackTrace();}}}}static class WaitClass implements Runnable {@Overridepublic void run() {synchronized (object) {System.out.println(Thread.currentThread() + "wait start....");try {object.wait();System.out.println(Thread.currentThread() + "wait end....");} catch (InterruptedException e) {e.printStackTrace();}}}}
}

?打印日志如下:

Thread[Thread-0,5,main]wait start....
Thread[Thread-1,5,main]notify start...
Thread[Thread-1,5,main]notify end...
Thread[Thread-0,5,main]wait end....

對于wait線程獲取到object對象的監視器之后,調用wait方法后進入等待隊列(WaitQueue),然后釋放監視器。

對于notify線程來說,先獲取到object對象監視器之后,然后調用notifyAll方法,將WaitQueue里面的所有等待線程同步到同步隊列中,(如果是調用notify方法就會值同步一個線程并非所有線程),然后釋放監視器,就會喚醒同步列隊中的線程。

對于wait線程,在同步隊列唄喚醒后,會重新獲取監視器,然后繼續執行wait方法后面的代碼。

這樣就完成一個等待通知的機制了。

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

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

相關文章

raidrive安裝失敗_記一次RaiDrive映射OneDrive遇到的問題

大概在1周以前,出于需要存放直播錄像的原因,根據別人的視頻教程去自己動手搞了個5T網盤的帳號。(體驗一下,其實我還同時存一份在百度云,怕不穩定)用RaiDrive創建OneDrive的映射,在這步驟點確定后,會彈出微軟…

通過代理模式 + 責任鏈模式實現對目標執行方法攔截和增強功能

前言 最近需要實現一個插件功能,但是如果做成兩個接口的話(即執行前和執行后),那么會降低插件的可玩性,所以需做成類似AOP的環繞通知形式,所以就使用到了責任鏈模式和代理模式進行實現。 介紹 代理模式(P…

Javascript基礎之-原型(prototype)

首先呢,prototype是對象里的一個內置屬性,并且呢,這個屬性是對于其他對象的一個引用。所以呢,思考下面的例子: var obj {a: 2 } var myObj Object.create(obj); console.log(myObj.a); // 2 console.log(myObj obj)…

Oracle查詢今天、昨天、本周、上周、本月、上月數據

查詢今天數據: SELECT COUNT(1) FROM T_CALL_RECORDS WHERE TO_CHAR(T_RKSJ,YYYY-MM-DD)TO_CHAR(SYSDATE,YYYY-MM-DD); 查詢昨天數據: SELECT COUNT(1) FROM T_CALL_RECORDS WHERE TO_CHAR(T_RKSJ,YYYY-MM-DD)TO_CHAR(SYSDATE-1,YYYY-MM-DD)&…

usb一轉多 樹莓派zero_樹莓派 Zero USB/以太網方式連接配置教程

樹莓派 Zero 之所以成為一款非常棒的單板計算機并不全因為它小巧的尺寸和便宜的價格,還得益于它便捷、易用的特性。在加裝了 Zero Quick Plug 或 microUSB/USB 轉換頭之后,將樹莓派 Zero 和電腦連接起來。樹莓派 Zero 即可配置成 USB/以太網設備&#xf…

vscode Go 1.11.4 編譯錯誤 need Delve built by Go 1.11 or later

更新golang的版本為1.11.4之后vscode編譯錯誤:executables built by Go 1.11 or later need Delve built by Go 1.11 or later 原因是delve的版本太老了,需要更新,且delve的github地址已經更換,很多教程里的地址是不對的 新地址安…

oppo的sd卡在哪里打開_oppo的sd卡在哪里打開

大家好,我是時間財富網智能客服時間君,上述問題將由我為大家進行解答。以oppo A91為例,其sd卡可直接在文件管理頁面的存儲里面即可打開。OPPO A91的屏幕為6.4英寸,主屏分辨率2400乘以1080像素,機身顏色有暗夜星辰&…

Navicat使用教程:使用Navicat Query Analyzer優化查詢性能(第1部分)

下載Navicat Monitor最新版本Navicat Monitor 是一套安全、簡單而且無代理的遠程服務器監控工具。它具有強大的功能使你的監控發揮最大效用。受監控的服務器包括 MySQL、MariaDB 和 Percona Server,并與 Amazon RDS、Amazon Aurora、Oracle Cloud、Microsoft Azure …

dg oracle 切換模式_Oracle數據庫 DGbroker三種保護模式的切換

1.三種保護模式– Maximum protection在Maximum protection下, 可以保證從庫和主庫數據完全一樣,做到zero data loss.事務同時在主從兩邊提交完成,才算事務完成。如果從庫宕機或者網絡出現問題,主從庫不能通訊,主庫也立…

軟件包管理

應用程序:程序:Architecture C語言:源代碼-->(編譯) 二進制格式腳本:解釋器(二進制程序) 源代碼-->編譯-->鏈接-->運行程序:指令數據指令:芯片CP…

工業機器人碼垛教學實施_工業機器人應用案例碼垛詳解

工業機器人應用案例碼垛詳解隨著科技的進步以及現代化進程的加快,人們對搬運速度的要求越來越高,傳統的人工碼垛只能應用在物料輕便、尺寸和形狀變化大、吞吐量小的場合,這已經遠遠不能滿足工業的需求,機器人碼垛機應運而生。機器…

第一家云創大數據產業學院在佛山職業技術學院掛牌

2019年1月10日,“云創大數據產業學院揭牌暨戰略合作協議簽署儀式”在佛山職業技術學院電子信息學院會議室舉行。云創大數據總裁劉鵬教授、市場部經理單明月,佛山職業技術學院電子信息學院院長唐建生、副院長田鈞、學院辦公室主任趙雪章、信息工程系主任喬…

String與StringBuffer和StringBuilder的根本區別

*************************************優雅的分割線 ********************************** 分享一波:程序員賺外快-必看的巔峰干貨 如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程 請關注微信公眾號:HB荷包 一個能讓你學習技術和賺錢方法的公眾號,持續更…

16進制 ksh_AIX系統中如何統計進程打開的文件數目

作者:李燁楠 中國建設銀行來自微信公眾號:平臺人生環境: AIX 6.1 AIX7.1前言:用戶有時需要統計一個進程打開的文件數目,比如,在當前打開文件句柄使用量是否超過用戶資源限制(/etc/security/limits)中 nofiles的取值時。那么&#…

前端Http協議緩存初解

[TOC] 簡介 用戶獲取網絡資源,需要通過非常長的網絡去服務器上請求資源,另外服務端為了應對大量的用戶請求而不斷的提升硬件性能與帶寬。這對用戶與服務端都非常的不友好。而緩存就是為了解決用戶請求速度與釋放服務器壓力而生的。 為什么我會寫Http緩存&#xff0c…

詳解java訪問修飾符

*************************************優雅的分割線 ********************************** 分享一波:程序員賺外快-必看的巔峰干貨 如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程 請關注微信公眾號:HB荷包 一個能讓你學習技術和賺錢方法的公眾號,持續更…

educoder 二進制數據的位運算_二進制與位運算實用操作匯總(基礎篇)

位運算是最高效而且占用內存最少的算法操作,但也是最難看懂的操作。然而,關于位運算的用法,筆者查了許多資料,似乎都沒有找到詳細而系統的講解資料。筆者對位運算的操作相當感興趣,因此斗膽嘗試對位運算來一個的總結。…

企業為什么要做SEO,它的重要性有哪些?

對于SEO工作而言,我們知道一個網站做SEO的基礎訴求就是讓用戶和搜索引擎更好的理解網站內容,雖然隨著搜索引擎算法技術的迭代,目前SEO面臨更大的挑戰與競爭,但基于搜索營銷,它目前仍然顯得十分重要。 那么&#xff0…

白話說編程之java線程

白話說編程之java線程線程和進程:進程:線程:線程和進程的區別:詳解多線程:并發為什么使用并發并發的執行原理并行線程的五種狀態:創建狀態:就緒狀態:運行狀態:阻塞狀態:死…

powerdesigner顯示工具面板_photoshop教程-畫筆工具預設與選項設置

定義畫筆預設在打開的“畫筆”面板中,單擊左側的“畫筆筆尖形狀”名稱,可顯示筆尖形狀圖案。單擊“畫筆”面板左側其他不同的選項名稱,在右側就會顯示其對應的調節項。只單擊不同選項前面的方框,可使此選項有效,但右側…