Java使用synchronized修飾方法來(lái)同步線程的實(shí)例演示
Java中可以使用關(guān)鍵字synchronized進(jìn)行線程同步控制,實(shí)現(xiàn)關(guān)鍵資源順序訪問(wèn),避免由于多線程并發(fā)執(zhí)行導(dǎo)致的數(shù)據(jù)不一致性等問(wèn)題。synchronized的原理是對(duì)象監(jiān)視器(鎖),只有獲取到監(jiān)視器的線程才能繼續(xù)執(zhí)行,否則線程會(huì)等待獲取監(jiān)視器。Java中每個(gè)對(duì)象或者類(lèi)都有一把鎖與之相關(guān)聯(lián),對(duì)于對(duì)象來(lái)說(shuō),監(jiān)視的是這個(gè)對(duì)象的實(shí)例變量,對(duì)于類(lèi)來(lái)說(shuō),監(jiān)視的是類(lèi)變量(一個(gè)類(lèi)本身是類(lèi)Class的對(duì)象,所以與類(lèi)關(guān)聯(lián)的鎖也是對(duì)象鎖)。synchronized關(guān)鍵字使用方式有兩種:synchronized方法和synchronized塊。這兩種監(jiān)視區(qū)域都和一個(gè)引入對(duì)象相關(guān)聯(lián),當(dāng)?shù)竭_(dá)這個(gè)監(jiān)視區(qū)域時(shí),JVM就會(huì)鎖住這個(gè)引用對(duì)象,當(dāng)離開(kāi)時(shí)會(huì)釋放這個(gè)引用對(duì)象上的鎖(有異常退出時(shí),JVM會(huì)釋放鎖)。對(duì)象鎖是JVM內(nèi)部機(jī)制,只需要編寫(xiě)同步方法或者同步塊即可,操作監(jiān)視區(qū)域時(shí)JVM會(huì)自動(dòng)獲取鎖或者釋放鎖。
示例1
先來(lái)看第一個(gè)示例,在java中,同一個(gè)對(duì)象的臨界區(qū),在同一時(shí)間只有一個(gè)允許被訪問(wèn)(都是非靜態(tài)的synchronized方法):
package concurrency; public class Main8 { public static void main(String[] args) { Account account = new Account(); account.setBalance(1000); Company company = new Company(account); Thread companyThread = new Thread(company); Bank bank = new Bank(account); Thread bankThread = new Thread(bank); System.out.printf("Account : Initial Balance: %f\n", account.getBalance()); companyThread.start(); bankThread.start(); try { //join()方法等待這兩個(gè)線程運(yùn)行完成 companyThread.join(); bankThread.join(); System.out.printf("Account : Final Balance: %f\n", account.getBalance()); } catch (InterruptedException e) { e.printStackTrace(); } } }
/*帳戶*/ class Account{ private double balance; /*將傳入的數(shù)據(jù)加到余額balance中*/ public synchronized void addAmount(double amount){ double tmp = balance; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } tmp += amount; balance = tmp; } /*將傳入的數(shù)據(jù)從余額balance中扣除*/ public synchronized void subtractAmount(double amount){ double tmp = balance; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } tmp -= amount; balance = tmp; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } }
/*銀行*/ class Bank implements Runnable{ private Account account; public Bank(Account account){ this.account = account; } @Override public void run() { for (int i = 0; i < 100; i++) { account.subtractAmount(1000); } } }
/*公司*/ class Company implements Runnable{ private Account account; public Company(Account account){ this.account = account; } @Override public void run() { for (int i = 0; i < 100; i++) { account.addAmount(1000); } } }
你已經(jīng)開(kāi)發(fā)了一個(gè)銀行賬戶的模擬應(yīng)用,它能夠?qū)τ囝~進(jìn)行充值和扣除。這個(gè)程序通過(guò)調(diào)用100次addAmount()方法對(duì)帳戶進(jìn)行充值,每次存入1000;然后通過(guò)調(diào)用100次subtractAmount()方法對(duì)帳戶余額進(jìn)行扣除,每次扣除1000;我們期望帳戶的最終余額與起初余額相等,我們通過(guò)synchronized關(guān)鍵字實(shí)現(xiàn)了。
如果想查看共享數(shù)據(jù)的并發(fā)訪問(wèn)問(wèn)題,只需要將addAmount()和subtractAmount()方法聲明中的synchronized關(guān)鍵字刪除即可。在沒(méi)有synchronized關(guān)鍵字的情況下,打印出來(lái)的余額值并不一致。如果多次運(yùn)行這個(gè)程序,你將獲取不同的結(jié)果。因?yàn)镴VM并不保證線程的執(zhí)行順序,所以每次運(yùn)行的時(shí)候,線程將以不同的順序讀取并且修改帳戶的余額,造成最終結(jié)果也是不一樣的。
一個(gè)對(duì)象的方法采用synchronized關(guān)鍵字進(jìn)行聲明,只能被一個(gè)線程訪問(wèn)。如果線程A正在執(zhí)行一個(gè)同步方法syncMethodA(),線程B要執(zhí)行這個(gè)對(duì)象的其他同步方法syncMethodB(),線程B將被阻塞直到線程A訪問(wèn)完。但如果線程B訪問(wèn)的是同一個(gè)類(lèi)的不同對(duì)象,那么兩個(gè)線程都不會(huì)被阻塞。
示例2
演示同一個(gè)對(duì)象上的靜態(tài)synchronized方法與非靜態(tài)synchronized方法可以在同一時(shí)間點(diǎn)被多個(gè)線程訪問(wèn)的問(wèn)題,驗(yàn)證一下。
package concurrency; public class Main8 { public static void main(String[] args) { Account account = new Account(); account.setBalance(1000); Company company = new Company(account); Thread companyThread = new Thread(company); Bank bank = new Bank(account); Thread bankThread = new Thread(bank); System.out.printf("Account : Initial Balance: %f\n", account.getBalance()); companyThread.start(); bankThread.start(); try { //join()方法等待這兩個(gè)線程運(yùn)行完成 companyThread.join(); bankThread.join(); System.out.printf("Account : Final Balance: %f\n", account.getBalance()); } catch (InterruptedException e) { e.printStackTrace(); } } }
/*帳戶*/ class Account{ /*這里改為靜態(tài)變量*/ private static double balance = 0; /*將傳入的數(shù)據(jù)加到余額balance中,注意是用static修飾過(guò)的*/ public static synchronized void addAmount(double amount){ double tmp = balance; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } tmp += amount; balance = tmp; } /*將傳入的數(shù)據(jù)從余額balance中扣除*/ public synchronized void subtractAmount(double amount){ double tmp = balance; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } tmp -= amount; balance = tmp; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } }
/*銀行*/ class Bank implements Runnable{ private Account account; public Bank(Account account){ this.account = account; } @Override public void run() { for (int i = 0; i < 100; i++) { account.subtractAmount(1000); } } }
/*公司*/ class Company implements Runnable{ private Account account; public Company(Account account){ this.account = account; } @Override public void run() { for (int i = 0; i < 100; i++) { account.addAmount(1000); } } }
我只是把上個(gè)例子中的,balance加了static關(guān)鍵字修改,addAmount()方法也可以static關(guān)鍵字修飾。執(zhí)行結(jié)果大家可以自己測(cè)試一下,每次執(zhí)行都是不一樣的結(jié)果!
一些總結(jié):
- synchronized是通過(guò)軟件(JVM)實(shí)現(xiàn)的,簡(jiǎn)單易用,即使在JDK5之后有了Lock,仍然被廣泛地使用。
- synchronized實(shí)際上是非公平的,新來(lái)的線程有可能立即獲得監(jiān)視器,而在等待區(qū)中等候已久的線程可能再次等待,不過(guò)這種搶占的方式可以預(yù)防饑餓。
- synchronized只有鎖只與一個(gè)條件(是否獲取鎖)相關(guān)聯(lián),不靈活,后來(lái)Condition與Lock的結(jié)合解決了這個(gè)問(wèn)題。
- 多線程競(jìng)爭(zhēng)一個(gè)鎖時(shí),其余未得到鎖的線程只能不停的嘗試獲得鎖,而不能中斷。高并發(fā)的情況下會(huì)導(dǎo)致性能下降。ReentrantLock的lockInterruptibly()方法可以優(yōu)先考慮響應(yīng)中斷。 一個(gè)線程等待時(shí)間過(guò)長(zhǎng),它可以中斷自己,然后ReentrantLock響應(yīng)這個(gè)中斷,不再讓這個(gè)線程繼續(xù)等待。有了這個(gè)機(jī)制,使用ReentrantLock時(shí)就不會(huì)像synchronized那樣產(chǎn)生死鎖了。
相關(guān)文章
基于mybatis batch實(shí)現(xiàn)批量提交大量數(shù)據(jù)
這篇文章主要介紹了基于mybatis batch實(shí)現(xiàn)批量提交大量數(shù)據(jù),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05Spring Boot 基于注解的 Redis 緩存使用詳解
本篇文章主要介紹了Spring Boot 基于注解的 Redis 緩存使用詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05Java在Excel中創(chuàng)建多級(jí)分組、折疊或展開(kāi)分組的實(shí)現(xiàn)
這篇文章主要介紹了Java在Excel中創(chuàng)建多級(jí)分組、折疊或展開(kāi)分組的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05解決spring boot hibernate 懶加載的問(wèn)題
這篇文章主要介紹了解決spring boot hibernate 懶加載的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10