java中synchronized關鍵字的用法

在java編程中,經常需要用到同步,而用得最多的也許是synchronized關鍵字了,下面看看這個關鍵字的用法。因為synchronized關鍵字涉及到鎖的概念,所以先來了解一些相關的鎖知識。java的內置鎖:每個java對象都可以用做一個實現同步的鎖,這些鎖成為內置鎖。線程進入同步代碼塊或方法的時候會自動獲得該鎖,在退出同步代碼塊或方法時會釋放該鎖。獲得內置鎖的唯一途徑就是進入這個鎖的保護的同步代碼塊或方法。java內置鎖是一個互斥鎖,這就是意味著最多只有一個線程能夠獲得該鎖,當線程A嘗試去獲得線程B持有的內置鎖時,線程A必須等待或者阻塞,知道線程B釋放這個鎖,如果B線程不釋放這個鎖,那么A線程將永遠等待下去。java的對象鎖和類鎖:java的對象鎖和類鎖在鎖的概念上基本上和內置鎖是一致的,但是,兩個鎖實際是有很大的區別的,對象鎖是用于對象實例方法,或者一個對象實例上的,類鎖是用于類的靜態方法或者一個類的class對象上的。我們知道,類的對象實例可以有很多個,但是每個類只有一個class對象,所以不同對象實例的對象鎖是互不干擾的,但是每個類只有一個類鎖。但是有一點必須注意的是,其實類鎖只是一個概念上的東西,并不是真實存在的,它只是用來幫助我們理解鎖定實例方法和靜態方法的區別的上面已經對鎖的一些概念有了一點了解,下面探討synchronized關鍵字的用法。synchronized的用法:synchronized修飾方法和synchronized修飾代碼塊。下面分別分析這兩種用法在對象鎖和類鎖上的效果。對象鎖的synchronized修飾方法和代碼塊:

public class TestSynchronized 
{  public void test1() {  synchronized(this) {  int i = 5;  while( i-- > 0) {  System.out.println(Thread.currentThread().getName() + " : " + i);  try {  Thread.sleep(500);  } catch (InterruptedException ie) {  }  }  }  }  public synchronized void test2() {  int i = 5;  while( i-- > 0) {  System.out.println(Thread.currentThread().getName() + " : " + i);  try {  Thread.sleep(500);  } catch (InterruptedException ie) {  }  }  }  public static void main(String[] args) {  final TestSynchronized myt2 = new TestSynchronized();  Thread test1 = new Thread(  new Runnable() {  public void run() {  myt2.test1();  }  }, "test1"  );  Thread test2 = new Thread(  new Runnable() {  public void run() { myt2.test2();   }  }, "test2"  );  test1.start();;  test2.start();  
//         TestRunnable tr=new TestRunnable();
//         Thread test3=new Thread(tr);
//         test3.start();} 
}test2 : 4
test2 : 3
test2 : 2
test2 : 1
test2 : 0
test1 : 4
test1 : 3
test1 : 2
test1 : 1
test1 : 0

上述的代碼,第一個方法時用了同步代碼塊的方式進行同步,傳入的對象實例是this,表明是當前對象,當然,如果需要同步其他對象實例,也不可傳入其他對象的實例;第二個方法是修飾方法的方式進行同步。因為第一個同步代碼塊傳入的this,所以兩個同步代碼所需要獲得的對象鎖都是同一個對象鎖,下面main方法時分別開啟兩個線程,分別調用test1和test2方法,那么兩個線程都需要獲得該對象鎖,另一個線程必須等待。上面也給出了運行的結果可以看到:直到test2線程執行完畢,釋放掉鎖,test1線程才開始執行。(可能這個結果有人會有疑問,代碼里面明明是先開啟test1線程,為什么先執行的是test2呢?這是因為java編譯器在編譯成字節碼的時候,會對代碼進行一個重排序,也就是說,編譯器會根據實際情況對代碼進行一個合理的排序,編譯前代碼寫在前面,在編譯后的字節碼不一定排在前面,所以這種運行結果是正常的, 這里是題外話,最主要是檢驗synchronized的用法的正確性)如果我們把test2方法的synchronized關鍵字去掉,執行結果會如何呢?test1 : 4
test2 : 4
test2 : 3
test1 : 3
test1 : 2
test2 : 2
test2 : 1
test1 : 1
test2 : 0
test1 : 0上面是執行結果,我們可以看到,結果輸出是交替著進行輸出的,這是因為,某個線程得到了對象鎖,但是另一個線程還是可以訪問沒有進行同步的方法或者代碼。進行了同步的方法(加鎖方法)和沒有進行同步的方法(普通方法)是互不影響的,一個線程進入了同步方法,得到了對象鎖,其他線程還是可以訪問那些沒有同步的方法(普通方法)。這里涉及到內置鎖的一個概念(此概念出自java并發編程實戰第二章):對象的內置鎖和對象的狀態之間是沒有內在的關聯的,雖然大多數類都將內置鎖用做一種有效的加鎖機制,但對象的域并不一定通過內置鎖來保護。當獲取到與對象關聯的內置鎖時,并不能阻止其他線程訪問該對象,當某個線程獲得對象的鎖之后,只能阻止其他線程獲得同一個鎖。之所以每個對象都有一個內置鎖,是為了免去顯式地創建鎖對象。所以synchronized只是一個內置鎖的加鎖機制,當某個方法加上synchronized關鍵字后,就表明要獲得該內置鎖才能執行,并不能阻止其他線程訪問不需要獲得該內置鎖的方法。類鎖的修飾(靜態)方法和代碼塊:

public class TestSynchronized 
{  public void test1() {  synchronized(TestSynchronized.class) {  int i = 5;  while( i-- > 0) {  System.out.println(Thread.currentThread().getName() + " : " + i);  try {  Thread.sleep(500);  } catch (InterruptedException ie) {  }  }  }  }  public static synchronized void test2() {  int i = 5;  while( i-- > 0) {  System.out.println(Thread.currentThread().getName() + " : " + i);  try {  Thread.sleep(500);  } catch (InterruptedException ie) {  }  }  }  public static void main(String[] args) {  final TestSynchronized myt2 = new TestSynchronized();  Thread test1 = new Thread(  new Runnable() {  public void run() {  myt2.test1();  }  }, "test1"  );  Thread test2 = new Thread(  new Runnable() {  public void run() { TestSynchronized.test2();   }  }, "test2"  );  test1.start();  test2.start();  
//         TestRunnable tr=new TestRunnable();
//         Thread test3=new Thread(tr);
//         test3.start();} 
}test1 : 4
test1 : 3
test1 : 2
test1 : 1
test1 : 0
test2 : 4
test2 : 3
test2 : 2
test2 : 1
test2 : 0

其實,類鎖修飾方法和代碼塊的效果和對象鎖是一樣的,因為類鎖只是一個抽象出來的概念,只是為了區別靜態方法的特點,因為靜態方法是所有對象實例共用的,所以對應著synchronized修飾的靜態方法的鎖也是唯一的,所以抽象出來個類鎖。其實這里的重點在下面這塊代碼,synchronized同時修飾靜態和非靜態方法

public class TestSynchronized 
{  public synchronized void test1() {  int i = 5;  while( i-- > 0) {  System.out.println(Thread.currentThread().getName() + " : " + i);  try {  Thread.sleep(500);  } catch (InterruptedException ie) {  }  }  }  public static synchronized void test2() {  int i = 5;  while( i-- > 0) {  System.out.println(Thread.currentThread().getName() + " : " + i);  try {  Thread.sleep(500);  } catch (InterruptedException ie) {  }  }  }  public static void main(String[] args) {  final TestSynchronized myt2 = new TestSynchronized();  Thread test1 = new Thread(  new Runnable() {  public void run() {  myt2.test1();  }  }, "test1"  );  Thread test2 = new Thread(  new Runnable() {  public void run() { TestSynchronized.test2();   }  }, "test2"  );  test1.start();  test2.start();  
//         TestRunnable tr=new TestRunnable();
//         Thread test3=new Thread(tr);
//         test3.start();} 
}test1 : 4
test2 : 4
test1 : 3
test2 : 3
test2 : 2
test1 : 2
test2 : 1
test1 : 1
test1 : 0
test2 : 0

上面代碼synchronized同時修飾靜態方法和實例方法,但是運行結果是交替進行的,這證明了類鎖和對象鎖是兩個不一樣的鎖,控制著不同的區域,它們是互不干擾的。同樣,線程獲得對象鎖的同時,也可以獲得該類鎖,即同時獲得兩個鎖,這是允許的。到這里,對synchronized的用法已經有了一定的了解。這時有一個疑問,既然有了synchronized修飾方法的同步方式,為什么還需要synchronized修飾同步代碼塊的方式呢?而這個問題也是synchronized的缺陷所在synchronized的缺陷:當某個線程進入同步方法獲得對象鎖,那么其他線程訪問這里對象的同步方法時,必須等待或者阻塞,這對高并發的系統是致命的,這很容易導致系統的崩潰。如果某個線程在同步方法里面發生了死循環,那么它就永遠不會釋放這個對象鎖,那么其他線程就要永遠的等待。這是一個致命的問題。當然同步方法和同步代碼塊都會有這樣的缺陷,只要用了synchronized關鍵字就會有這樣的風險和缺陷。既然避免不了這種缺陷,那么就應該將風險降到最低。這也是同步代碼塊在某種情況下要優于同步方法的方面。例如在某個類的方法里面:這個類里面聲明了一個對象實例,SynObject so=new SynObject();在某個方法里面調用了這個實例的方法so.testsy();但是調用這個方法需要進行同步,不能同時有多個線程同時執行調用這個方法。這時如果直接用synchronized修飾調用了so.testsy();代碼的方法,那么當某個線程進入了這個方法之后,這個對象其他同步方法都不能給其他線程訪問了。假如這個方法需要執行的時間很長,那么其他線程會一直阻塞,影響到系統的性能。如果這時用synchronized來修飾代碼塊:synchronized(so){so.testsy();},那么這個方法加鎖的對象是so這個對象,跟執行這行代碼的對象沒有關系,當一個線程執行這個方法時,這對其他同步方法時沒有影響的,因為他們持有的鎖都完全不一樣。不過這里還有一種特例,就是上面演示的第一個例子,對象鎖synchronized同時修飾方法和代碼塊,這時也可以體現到同步代碼塊的優越性,如果test1方法同步代碼塊后面有非常多沒有同步的代碼,而且有一個100000的循環,這導致test1方法會執行時間非常長,那么如果直接用synchronized修飾方法,那么在方法沒執行完之前,其他線程是不可以訪問test2方法的,但是如果用了同步代碼塊,那么當退出代碼塊時就已經釋放了對象鎖,當線程還在執行test1的那個100000的循環時,其他線程就已經可以訪問test2方法了。這就讓阻塞的機會或者線程更少。讓系統的性能更優越。一個類的對象鎖和另一個類的對象鎖是沒有關聯的,當一個線程獲得A類的對象鎖時,它同時也可以獲得B類的對象鎖。可能上面只有理論和代碼,對剛接觸的人比較難理解,下面舉一個例子,打個比方:一個object就像一個大房子,大門永遠打開。房子里有 很多房間(也就是方法)。這些房間有上鎖的(synchronized方法), 和不上鎖之分(普通方法)。房門口放著一把鑰匙(key),這把鑰匙可以打開所有上鎖的房間。
另外我把所有想調用該對象方法的線程比喻成想進入這房子某個 房間的人。所有的東西就這么多了,下面我們看看這些東西之間如何作用的。在此我們先來明確一下我們的前提條件。該對象至少有一個synchronized方法,否則這個key還有啥意義。當然也就不會有我們的這個主題了。一個人想進入某間上了鎖的房間,他來到房子門口,看見鑰匙在那兒(說明暫時還沒有其他人要使用上鎖的 房間)。于是他走上去拿到了鑰匙,并且按照自己 的計劃使用那些房間。注意一點,他每次使用完一次上鎖的房間后會馬上把鑰匙還回去。即使他要連續使用兩間上鎖的房間,中間他也要把鑰匙還回去,再取回來。因此,普通情況下鑰匙的使用原則是:“隨用隨借,用完即還。”這時其他人可以不受限制的使用那些不上鎖的房間,一個人用一間可以,兩個人用一間也可以,沒限制。但是如果當某個人想要進入上鎖的房間,他就要跑到大門口去看看了。有鑰匙當然拿了就走,沒有的話,就只能等了。要是很多人在等這把鑰匙,等鑰匙還回來以后,誰會優先得到鑰匙?Not guaranteed。象前面例子里那個想連續使用兩個上鎖房間的家伙,他中間還鑰匙的時候如果還有其他人在等鑰匙,那么沒有任何保證這家伙能再次拿到。 (JAVA規范在很多地方都明確說明不保證,像Thread.sleep()休息后多久會返回運行,相同優先權的線程那個首先被執行,當要訪問對象的鎖被 釋放后處于等待池的多個線程哪個會優先得到,等等。我想最終的決定權是在JVM,之所以不保證,就是因為JVM在做出上述決定的時候,絕不是簡簡單單根據 一個條件來做出判斷,而是根據很多條。而由于判斷條件太多,如果說出來可能會影響JAVA的推廣,也可能是因為知識產權保護的原因吧。SUN給了個不保證 就混過去了。無可厚非。但我相信這些不確定,并非完全不確定。因為計算機這東西本身就是按指令運行的。即使看起來很隨機的現象,其實都是有規律可尋。學過 計算機的都知道,計算機里隨機數的學名是偽隨機數,是人運用一定的方法寫出來的,看上去隨機罷了。另外,或許是因為要想弄的確太費事,也沒多大意義,所 以不確定就不確定了吧。)再來看看同步代碼塊。和同步方法有小小的不同。1.從尺寸上講,同步代碼塊比同步方法小。你可以把同步代碼塊看成是沒上鎖房間里的一塊用帶鎖的屏風隔開的空間。2.同步代碼塊還可以人為的指定獲得某個其它對象的key。就像是指定用哪一把鑰匙才能開這個屏風的鎖,你可以用本房的鑰匙;你也可以指定用另一個房子的鑰匙才能開,這樣的話,你要跑到另一棟房子那兒把那個鑰匙拿來,并用那個房子的鑰匙來打開這個房子的帶鎖的屏風。記住你獲得的那另一棟房子的鑰匙,并不影響其他人進入那棟房子沒有鎖的房間。為什么要使用同步代碼塊呢?      我想應該是這樣的:首先對程序來講同步的部分很影響運行效率,而一個方法通常是先創建一些局部變量,再對這些變量做一些 操作,如運算,顯示等等;而同步所覆蓋的代碼越多,對效率的影響就越嚴重。因此我們通常盡量縮小其影響范圍。如何做?同步代碼塊。我們只把一個方法中該同 步的地方同步,比如運算。另外,同步代碼塊可以指定鑰匙這一特點有個額外的好處,是可以在一定時期內霸占某個對象的key。還記得前面說過普通情況下鑰匙的使用原則嗎。現在不是普通情況了。你所取得的那把鑰匙不是永遠不還,而是在退出同步代碼塊時才還。還用前面那個想連續用兩個上鎖房間的家伙打比方。怎樣才能在用完一間以后,繼續使用另一間呢。用同步代碼塊吧。先創建另外一個線程,做一個同步代碼 塊,把那個代碼塊的鎖指向這個房子的鑰匙。然后啟動那個線程。只要你能在進入那個代碼塊時抓到這房子的鑰匙,你就可以一直保留到退出那個代碼塊。也就是說 你甚至可以對本房內所有上鎖的房間遍歷,甚至再sleep(10*60*1000),而房門口卻還有1000個線程在等這把鑰匙呢。很過癮吧。大概就這么多了

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

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

相關文章

在Pytorch中使用Tensorboard可視化訓練過程

這篇是我對嗶哩嗶哩up主 霹靂吧啦Wz 的視頻的文字版學習筆記 感謝他對知識的分享 本節課我們來講一下如何在pytouch當中去使用我們的tensorboard 對我們的訓練過程進行一個可視化 左邊有一個visualizing models data and training with tensorboard 主要是這么一個教程 那么這里…

Flutter一直 Running Gradle task ‘assembleDebug‘

Flutter升級到3.13.7之后,一直Running Gradle task ‘assembleDebug’,之前運行還沒問題。 試了各種方法,比如添加阿里云鏡像,flutter\packages\flutter_tools\gradle目錄下修改build.gradle.kts文件,都不行。 參考大佬…

Termux+Hexo結合內網穿透輕松實現安卓手機搭建博客網站發布公網訪問

文章目錄 前言 1.安裝 Hexo2.安裝cpolar3.遠程訪問4.固定公網地址 前言 Hexo 是一個用 Nodejs 編寫的快速、簡潔且高效的博客框架。Hexo 使用 Markdown 解析文章,在幾秒內,即可利用靚麗的主題生成靜態網頁。 下面介紹在Termux中安裝個人hexo博客并結合…

ArkTS語言難嗎?鴻蒙指南

HarmonyOS的開發語言是ArkTS、JS(JavaScript)。 ArkTS簡介 ArkTS是HarmonyOS優選的主力應用開發語言。ArkTS圍繞應用開發在TypeScript(簡稱TS)生態基礎上做了進一步擴展,繼承了TS的所有特性,是TS的超集。因此,在學習…

骨傳導耳機品牌排行榜哪家強?盤點2024年骨傳導耳機排行榜前十

隨著科技的發展,耳機已經從單純的音樂播放工具,演變為我們生活中不可或缺的伴侶。其中,骨傳導耳機憑借其獨特的傳聲方式,逐漸受到越來越多消費者的青睞。骨傳導耳機通過骨頭直接傳遞聲音,避免了外耳道感染和中耳疾病&a…

Java中不同轉換符實現不同數據類型到字符串的轉換

String類的format()方法用于創建格式化的字符串以及連接多個字符串對象。熟悉C語言的同學應該記得C語言的sprintf()方法,兩者有類似之處。format()方法有兩種重載形式。 format(String format, Object... args) 新字符串使用本地語言環境,制定字符串格式…

(企業項目)SpringBoot實現雪花算法id注冊功能

以下是使用 Spring Boot 實現雪花算法的完整代碼。 import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;SpringBootApplication public class SnowflakeGeneratorApplication {public static void mai…

Java 簡易版 TCP(一對一)聊天

客戶端 import java.io.*; import java.net.Socket; import java.util.Date; import javax.swing.*;public class MyClient {private JFrame jf;private JButton jBsend;private JTextArea jTAcontent;private JTextField jText;private JLabel JLcontent;private Date data;p…

刪除有序數組中的重復元素-練習雙指針編程最好的一道題-費元星

這是練習編成雙指針最好的一道題,邏輯簡單,優化簡單。 簡單理解:采用雙指針,總左邊進行開始,如果有新元素則將前面移動到后面。然后加一個剪枝操作,如果前后元素大于1,再移動。 給你一個 非嚴格…

檢驗科LIS系統源碼,臨床檢驗數據的采集、存貯、處理、提取、傳輸、匯總

檢驗科LIS系統源碼 臨床檢驗信息管理系統是醫院信息管理系統的重要組成部分,實現對LIS系統所涉及的人流、物流、財流進行綜合管理,對在臨床檢驗活動各階段中產生的數據進行采集、存貯、處理、提取、傳輸、匯總、加工生成各種信息,從而為醫院的…

freeswitch編譯mod_av支持webrtc MCU通話

系統環境 一、FS相關網站 二、第三方庫安裝 1.apt安裝 2.指定版本sofia-sip安裝 3.指定版本spandsp安裝 4.指定版本libks安裝 5.指定版本openssl安裝 三、指定版本FS安裝 1.CPPFLAGS配置 2.編譯器版本 3.FS配置編譯 四、FS,fs_cli運行,模塊加載 附錄 1.安…

word一鍵接受所有修訂并保留修訂痕跡

目的:讓word修訂插入的內容在接受修訂后保留痕跡。 文章目錄 目的:讓word修訂插入的內容在接受修訂后保留痕跡。1. 打開批注的word文件2. 同時按住:*AltF11*,然后右鍵:Normal -->插入--> 模塊3. 在出現的代碼框中…

代碼隨想錄算法訓練營第五十九天【單調棧part2】 | 503.下一個更大元素II、42. 接雨水

503.下一個更大元素II 題目鏈接 力扣(LeetCode)官網 - 全球極客摯愛的技術成長平臺 求解思路 重點在如何處理循環數組。 方案一: 直接將兩個數組拼接在一起,然后使用單調棧求下一個最大值。 方案二: 在遍歷的過…

elementUI中的 “this.$confirm“ 基本用法,“this.$confirm“ 調換 “確認“、“取消“ 按鈕的位置

文章目錄 前言具體操作總結 前言 elementUI中的 "this.$confirm" 基本用法&#xff0c;"this.$confirm" 調換 "確認"、"取消" 按鈕的位置 具體操作 基本用法 <script> this.$confirm(這是數據&#xff08;res.data&#xff0…

使用ASIRequest庫進行Objective-C網絡爬蟲示例

在Objective-C中&#xff0c;ASIHTTPRequest是一個非常受歡迎的庫&#xff0c;用于處理HTTP請求。它可用于下載網頁內容&#xff0c;處理API請求&#xff0c;甚至進行復雜的網絡交互。下面是一個簡單的示例&#xff0c;展示了如何使用ASIHTTPRequest庫來爬取網頁代碼。 首先&a…

使用項目管理工具進行新媒體運營管理的策略與方法

使用Zoho Projects項目管理工具&#xff0c;新媒體運營可輕松駕馭從策劃選題、撰寫到排期發布的全流程。運用項目管理工具對新媒體運營進行精細化管理&#xff0c;助力團隊更高效地規劃、執行和追蹤各項任務與活動。 以下是運用項目管理工具管理新媒體運營的妙招&#xff1a; 1…

oracle 下載java之前版本

登錄oracle官網&#xff1a;Oracle | Cloud Applications and Cloud Platform 點擊resource 進入該頁面 點擊這個 出現之前版本

初識Linux:權限(2)

目錄 權限 用戶&#xff08;角色&#xff09; 文件權限屬性 文件的權限屬性&#xff1a; 有無權限的區別&#xff1a; 身份匹配&#xff1a; 擁有者、所屬組的修改&#xff1a; 八進制的轉化&#xff1a; 文件的類型&#xff1a; x可執行權限為什么不能執行&#xf…

03DockerFile

03DockerFile 1.DockerFile的概念 用來構建docker?鏡像的構建文件,由一系列參數和命令構成的腳本 大體總覽: ?? ? 1.構建過程 要遵循的規則: ?? 2.執行流程 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

Java并發編程指南:實現高效并發操作

引言&#xff1a; 在當今的軟件開發領域&#xff0c;多核處理器的普及使得并發編程成為了一個重要的話題。并發編程是指多個線程同時執行不同的任務&#xff0c;以提高程序的性能和響應能力。然而&#xff0c;并發編程也帶來了一系列的挑戰&#xff0c;如線程安全、死鎖等問題。…