Java多線程編程中synchronized關(guān)鍵字的基礎(chǔ)用法講解
多線程編程中,最關(guān)鍵、最關(guān)心的問題應(yīng)該就是同步問題,這是一個難點(diǎn),也是核心。
從jdk最早的版本的synchronized、volatile,到j(luò)dk 1.5中提供的java.util.concurrent.locks包中的Lock接口(實(shí)現(xiàn)有ReadLock,WriteLock,ReentrantLock),多線程的實(shí)現(xiàn)也是一步步走向成熟化。
同步,它是通過什么機(jī)制來控制的呢?第一反應(yīng)就是鎖,這個在學(xué)習(xí)操作系統(tǒng)與數(shù)據(jù)庫的時候,應(yīng)該都已經(jīng)接觸到了。在Java的多線程程序中,當(dāng)多個程序競爭同一個資源時,為了防止資源的腐蝕,給第一個訪問資源的線程分配一個對象鎖,而后來者需要等待這個對象鎖的釋放。
是的,Java線程的同步,最關(guān)心的是共享資源的使用。
先來了解一些有哪些線程的共享資源,
從JVM中了解有哪些線程共享的數(shù)據(jù)是需要進(jìn)行協(xié)調(diào):
1,保存在堆中的實(shí)例變量;2,保存在方法區(qū)的類變量。
而在Java虛擬機(jī)加載類的時候,每個對象或類都會與一個監(jiān)視器相關(guān)聯(lián),用來保護(hù)對象的實(shí)例變量或類變量;當(dāng)然,如果對象沒有實(shí)例變量,或類沒有變量,監(jiān)視器就什么也不監(jiān)視了。
為了實(shí)現(xiàn)上面的說的監(jiān)視器的互斥性,虛擬機(jī)為每一個對象或類都關(guān)聯(lián)了一個鎖(也叫隱形鎖),這里說明一下,類鎖也是通過對象鎖來實(shí)現(xiàn)的,因?yàn)樵陬惣虞d的時候,JVM會為每一個類創(chuàng)建一個java.lang.Class的一個實(shí)例;所以當(dāng)鎖對對象的時候,也就鎖住這個類的類對象。
另外,一個線程是可以對一個對象進(jìn)行多次上鎖,也就對應(yīng)著多次釋放;它是通過JVM為每個對象鎖提供的lock計算器,上一次鎖,就加1,對應(yīng)的減1,當(dāng)計算器的值為0時,就釋放。這個對象鎖是JVM內(nèi)部的監(jiān)視器使用的,也是由JVM自動生成的,所有程序猿就不用自己動手來加了。
介紹完java的同步原理后,我們進(jìn)入正題,先來說說synchronized的使用,而其它的同步,將在后面的章節(jié)中介紹。
先來運(yùn)行一個例子試試。
package thread_test; /** * 測試擴(kuò)展Thread類實(shí)現(xiàn)的多線程程序 * */ public class TestThread extends Thread{ private int threadnum; public TestThread(int threadnum) { this.threadnum = threadnum; } @Override public synchronized void run() { for(int i = 0;i<1000;i++){ System.out.println("NO." + threadnum + ":" + i ); } } public static void main(String[] args) throws Exception { for(int i=0; i<10; i++){ new TestThread(i).start(); Thread.sleep(1); } } }
運(yùn)行結(jié)果:
NO.0:887 NO.0:888 NO.0:889 NO.0:890 NO.0:891 NO.0:892 NO.0:893 NO.0:894 NO.7:122 NO.7:123 NO.7:124
上面只是一個片段,說明一個問題而已。
細(xì)心的童鞋會發(fā)現(xiàn),NO.0:894后面是NO.7:122,也就是說沒有按照從0開始到999。
都說synchronized可以實(shí)現(xiàn)同步方法或同步塊,這里怎么就不行呢?
先從同步的機(jī)制來分析一下,同步是通過鎖來實(shí)現(xiàn)的,那么上面的例子中,鎖定了什么對象,或鎖定了什么類呢?里面有兩個變量,一個是i,一個是threadnum;i是方法內(nèi)部的,threadnum是私有的。
再來了解一下synchronized的運(yùn)行機(jī)制:
在java程序中,當(dāng)使用synchronized塊或synchronized方法時,標(biāo)志這個區(qū)域進(jìn)行監(jiān)視;而JVM在處理程序時,當(dāng)有程序進(jìn)入監(jiān)視區(qū)域時,就會自動鎖上對象或類。
那么上面的例子中,synchronized關(guān)鍵字用上后,鎖定的是什么呢?
當(dāng)synchronized方法時,鎖定調(diào)用方法的實(shí)例對象本身做為對象鎖。本例中,10個線程都有自己創(chuàng)建的TestThread的類對象,所以獲取的對象鎖,也是自己的對象鎖,與其它線程沒有任何關(guān)系。
要實(shí)現(xiàn)方法鎖定,必須鎖定有共享的對象。
對上面的實(shí)例修改一下,再看看:
package thread_test; /** * 測試擴(kuò)展Thread類實(shí)現(xiàn)的多線程程序 * */ public class TestThread extends Thread{ private int threadnum; private String flag; //標(biāo)記 public TestThread(int threadnum,String flag) { this.threadnum = threadnum; this.flag = flag; } @Override public void run() { synchronized(flag){ for(int i = 0;i<1000;i++){ System.out.println("NO." + threadnum + ":" + i ); } } } public static void main(String[] args) throws Exception { String flag = new String("flag"); for(int i=0; i<10; i++){ new TestThread(i,flag).start(); Thread.sleep(1); } } }
也就加了一個共享的標(biāo)志flag。然后在通過synchronized塊,對flag標(biāo)志進(jìn)行同步;這就滿足了鎖定共享對象的條件。
是的,運(yùn)行結(jié)果,已經(jīng)按順序來了。
通過synchronized塊,指定獲取對象鎖來達(dá)到同步的目的。那有沒有其它的方法,可以通過synchronized方法來實(shí)現(xiàn)呢?
根據(jù)同步的原理:如果能獲取一個共享對象鎖或類鎖,及可實(shí)現(xiàn)同步。那么我們是不是可以通過共享一個類鎖來實(shí)現(xiàn)呢?
是的,我們可以使用靜態(tài)同步方法,根據(jù)靜態(tài)方法的特性,它只允許類對象本身才可以調(diào)用,不能通過實(shí)例化一個類對象來調(diào)用。那么如果獲得了這個靜態(tài)方法的鎖,也就是獲得這個類鎖,而這個類鎖都是TestThread類鎖,及達(dá)到了獲取共享類鎖的目的。
實(shí)現(xiàn)代碼如下:
package thread_test; /** * 測試擴(kuò)展Thread類實(shí)現(xiàn)的多線程程序 * * @author ciding * @createTime Dec 7, 2011 9:37:25 AM * */ public class TestThread extends Thread{ private int threadnum; public TestThread(int threadnum) { this.threadnum = threadnum; } public static synchronized void staticTest(int threadnum) { for(int i = 0;i<1000;i++){ System.out.println("NO." + threadnum + ":" + i ); } } public static void main(String[] args) throws Exception { for(int i=0; i<10; i++){ new TestThread(i).start(); Thread.sleep(1); } } @Override public void run(){ staticTest(threadnum); } }
運(yùn)行結(jié)果略,與第二個例子中一樣。
以上的內(nèi)容主要是說明兩個問題:同步塊與同步方法。
1,同步塊:獲取的對象鎖是synchronized(flag)中的flag對象鎖。
2,同步方法:獲取的是方法所屬的類對象,及類對象鎖。
靜態(tài)同步方法,由于多個線程都會共享,所以一定會同步。
而非靜態(tài)同步方法,只有在單例模式下才會同步。
相關(guān)文章
Springboot使用put、delete請求報錯405的處理
這篇文章主要介紹了Springboot使用put、delete請求報錯405的處理方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07idea中springboot整合mybatis找不到mapper接口的原因分析
這篇文章主要介紹了idea中springboot整合mybatis找不到mapper接口的原因分析及解決,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01Java實(shí)現(xiàn)ATM系統(tǒng)超全面步驟解讀建議收藏
這篇文章主要為大家詳細(xì)介紹了用Java實(shí)現(xiàn)簡單ATM機(jī)功能,文中實(shí)現(xiàn)流程寫的非常清晰全面,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03java使用Filter實(shí)現(xiàn)自動登錄的方法
這篇文章主要為大家詳細(xì)介紹了java使用Filter實(shí)現(xiàn)自動登錄的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04Java設(shè)置httponly?cookie的實(shí)現(xiàn)示例
本文主要介紹了Java設(shè)置httponly?cookie的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08