Java 實(shí)現(xiàn)線程安全的三種方式
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
? 作者?|? 潘彬
來源 |? urlify.cn/jARjuq
66套java從入門到精通實(shí)戰(zhàn)課程分享
一個(gè)程序在運(yùn)行起來的時(shí)候會(huì)轉(zhuǎn)換成進(jìn)程,通常含有多個(gè)線程。
通常情況下,一個(gè)進(jìn)程中的比較耗時(shí)的操作(如長循環(huán)、文件上傳下載、網(wǎng)絡(luò)資源獲取等),往往會(huì)采用多線程來解決。
比如顯示生活中,銀行取錢問題、火車票多個(gè)售票窗口的問題,通常會(huì)涉及到并發(fā)的問題,從而需要多線程的技術(shù)。
當(dāng)進(jìn)程中有多個(gè)并發(fā)線程進(jìn)入一個(gè)重要數(shù)據(jù)的代碼塊時(shí),在修改數(shù)據(jù)的過程中,很有可能引發(fā)線程安全問題,從而造成數(shù)據(jù)異常。例如,正常邏輯下,同一個(gè)編號(hào)的火車票只能售出一次,卻由于線程安全問題而被多次售出,從而引起實(shí)際業(yè)務(wù)異常。
?
現(xiàn)在我們就以售票問題來演示線程安全的問題
1, 在不對(duì)多線程數(shù)據(jù)進(jìn)行保護(hù)的情況下會(huì)引發(fā)的狀況
public?class?ThreadUnSecurity?{
????
????static?int?tickets?=?10;
????
????class?SellTickets?implements?Runnable{
????????@Override
????????public?void?run()?{
????????????//?未加同步時(shí)產(chǎn)生臟數(shù)據(jù)
????????????while(tickets?>?0)?{
????????????????
????????????????System.out.println(Thread.currentThread().getName()+"--->售出第:??"+tickets+"?票");
????????????????tickets--;
????????????????
????????????????try?{
????????????????????Thread.sleep(1000);
????????????????}?catch?(InterruptedException?e)?{
????????????????????e.printStackTrace();
????????????????}
????????????????
????????????}
????????????
????????????if?(tickets?<=?0)?{
????????????????
????????????????System.out.println(Thread.currentThread().getName()+"--->售票結(jié)束!");
????????????}
????????}
????}
????
????
????public?static?void?main(String[]?args)?{
????????
????????
????????SellTickets?sell?=?new?ThreadUnSecurity().new?SellTickets();
????????
????????Thread?thread1?=?new?Thread(sell,?"1號(hào)窗口");
????????Thread?thread2?=?new?Thread(sell,?"2號(hào)窗口");
????????Thread?thread3?=?new?Thread(sell,?"3號(hào)窗口");
????????Thread?thread4?=?new?Thread(sell,?"4號(hào)窗口");
????????
????????thread1.start();
????????thread2.start();
????????thread3.start();
????????thread4.start();
????????
????????
????}
????
}
上述代碼運(yùn)行的結(jié)果:
1號(hào)窗口--->售出第:? 10?票
3號(hào)窗口--->售出第:? 10?票
2號(hào)窗口--->售出第:? 10?票
4號(hào)窗口--->售出第:? 10?票
2號(hào)窗口--->售出第:? 6 票
1號(hào)窗口--->售出第:? 5 票
3號(hào)窗口--->售出第:? 4 票
4號(hào)窗口--->售出第:? 3 票
2號(hào)窗口--->售出第:? 2 票
4號(hào)窗口--->售出第:? 1 票
1號(hào)窗口--->售出第:? 1 票
3號(hào)窗口--->售票結(jié)束!
2號(hào)窗口--->售票結(jié)束!
1號(hào)窗口--->售票結(jié)束!
4號(hào)窗口--->售票結(jié)束!
我們可以看出同一張票在不對(duì)票數(shù)進(jìn)行保護(hù)時(shí)會(huì)出現(xiàn)同一張票會(huì)被出售多次!由于線程調(diào)度中的不確定性,讀者在演示上述代碼時(shí),出現(xiàn)的運(yùn)行結(jié)果會(huì)有不同。
?
第一種實(shí)現(xiàn)線程安全的方式
同步代碼塊
package?com.bpan.spring.beans.thread;
import?com.sun.org.apache.regexp.internal.recompile;
public?class?ThreadSynchronizedSecurity?{
????
????static?int?tickets?=?10;
????
????class?SellTickets?implements?Runnable{
????????@Override
????????public?void?run()?{
????????????//?同步代碼塊
????????????while(tickets?>?0)?{
????????????????
????????????????synchronized?(this)?{
????????????????????
//????????????????????System.out.println(this.getClass().getName().toString());
????????????????????
????????????????????if?(tickets?<=?0)?{
????????????????????????
????????????????????????return;
????????????????????}
????????????????????
????????????????????System.out.println(Thread.currentThread().getName()+"--->售出第:??"+tickets+"?票");
????????????????????tickets--;
????????????????????
????????????????????try?{
????????????????????????Thread.sleep(100);
????????????????????}?catch?(InterruptedException?e)?{
????????????????????????e.printStackTrace();
????????????????????}
????????????????}
????????????????
????????????????if?(tickets?<=?0)?{
????????????????????
????????????????????System.out.println(Thread.currentThread().getName()+"--->售票結(jié)束!");
????????????????}
????????????}
????????}
????}
????
????
????public?static?void?main(String[]?args)?{
????????
????????
????????SellTickets?sell?=?new?ThreadSynchronizedSecurity().new?SellTickets();
????????
????????Thread?thread1?=?new?Thread(sell,?"1號(hào)窗口");
????????Thread?thread2?=?new?Thread(sell,?"2號(hào)窗口");
????????Thread?thread3?=?new?Thread(sell,?"3號(hào)窗口");
????????Thread?thread4?=?new?Thread(sell,?"4號(hào)窗口");
????????
????????thread1.start();
????????thread2.start();
????????thread3.start();
????????thread4.start();
????????
????????
????}
????
}
輸出結(jié)果讀者可自行調(diào)試,不會(huì)出現(xiàn)同一張票被出售多次的情況。
?
第二種 方式
同步方法
package?com.bpan.spring.beans.thread;
public?class?ThreadSynchroniazedMethodSecurity?{
????
????
????static?int?tickets?=?10;
????
????class?SellTickets?implements?Runnable{
????????@Override
????????public?void?run()?{
????????????//同步方法
????????????while?(tickets?>?0)?{
????????????????
????????????????synMethod();
????????????????
????????????????try?{
????????????????????Thread.sleep(100);
????????????????}?catch?(InterruptedException?e)?{
????????????????????//?TODO?Auto-generated?catch?block
????????????????????e.printStackTrace();
????????????????}
????????????????
????????????????if?(tickets<=0)?{
????????????????????
????????????????????System.out.println(Thread.currentThread().getName()+"--->售票結(jié)束");
????????????????}
????????????????
????????????}
????????????
????????????
????????}
????????
????????synchronized?void?synMethod()?{
????????????
????????????synchronized?(this)?{
????????????????if?(tickets?<=0)?{
????????????????????
????????????????????return;
????????????????}
????????????????
????????????????System.out.println(Thread.currentThread().getName()+"---->售出第?"+tickets+"?票?");
????????????????tickets--?;
????????????}
????????????
????????}
????????
????}
????public?static?void?main(String[]?args)?{
????????
????????
????????SellTickets?sell?=?new?ThreadSynchroniazedMethodSecurity().new?SellTickets();
????????
????????Thread?thread1?=?new?Thread(sell,?"1號(hào)窗口");
????????Thread?thread2?=?new?Thread(sell,?"2號(hào)窗口");
????????Thread?thread3?=?new?Thread(sell,?"3號(hào)窗口");
????????Thread?thread4?=?new?Thread(sell,?"4號(hào)窗口");
????????
????????thread1.start();
????????thread2.start();
????????thread3.start();
????????thread4.start();
????????
????}
}
讀者可自行調(diào)試上述代碼的運(yùn)行結(jié)果
?
第三種 方式
Lock鎖機(jī)制, 通過創(chuàng)建Lock對(duì)象,采用lock()加鎖,unlock()解鎖,來保護(hù)指定的代碼塊
package?com.bpan.spring.beans.thread;
import?java.util.concurrent.locks.Lock;
import?java.util.concurrent.locks.ReentrantLock;
public?class?ThreadLockSecurity?{
????
????static?int?tickets?=?10;
????
????class?SellTickets?implements?Runnable{
????????
????????Lock?lock?=?new?ReentrantLock();
????????@Override
????????public?void?run()?{
????????????//?Lock鎖機(jī)制
????????????while(tickets?>?0)?{
????????????????
????????????????try?{
????????????????????lock.lock();
????????????????????
????????????????????if?(tickets?<=?0)?{
????????????????????????
????????????????????????return;
????????????????????}
????????????????????????
????????????????????System.out.println(Thread.currentThread().getName()+"--->售出第:??"+tickets+"?票");
????????????????????tickets--;
????????????????}?catch?(Exception?e1)?{
????????????????????//?TODO?Auto-generated?catch?block
????????????????????e1.printStackTrace();
????????????????}finally?{
????????????????????
????????????????????lock.unlock();
????????????????????try?{
????????????????????????Thread.sleep(100);
????????????????????}?catch?(InterruptedException?e)?{
????????????????????????e.printStackTrace();
????????????????????}
????????????????}
????????????}
????????????????
????????????if?(tickets?<=?0)?{
????????????????
????????????????System.out.println(Thread.currentThread().getName()+"--->售票結(jié)束!");
????????????}
????????????
????????}
????}
????
????
????public?static?void?main(String[]?args)?{
????????
????????
????????SellTickets?sell?=?new?ThreadLockSecurity().new?SellTickets();
????????
????????Thread?thread1?=?new?Thread(sell,?"1號(hào)窗口");
????????Thread?thread2?=?new?Thread(sell,?"2號(hào)窗口");
????????Thread?thread3?=?new?Thread(sell,?"3號(hào)窗口");
????????Thread?thread4?=?new?Thread(sell,?"4號(hào)窗口");
????????
????????thread1.start();
????????thread2.start();
????????thread3.start();
????????thread4.start();
????????
????????
????}
????
}
最后總結(jié):
由于synchronized是在JVM層面實(shí)現(xiàn)的,因此系統(tǒng)可以監(jiān)控鎖的釋放與否;而ReentrantLock是使用代碼實(shí)現(xiàn)的,系統(tǒng)無法自動(dòng)釋放鎖,需要在代碼中的finally子句中顯式釋放鎖lock.unlock()。? ??
另外,在并發(fā)量比較小的情況下,使用synchronized是個(gè)不錯(cuò)的選擇;但是在并發(fā)量比較高的情況下,其性能下降會(huì)很嚴(yán)重,此時(shí)ReentrantLock是個(gè)不錯(cuò)的方案。
?
補(bǔ)充:
在使用synchronized 代碼塊時(shí),可以與wait()、notify()、nitifyAll()一起使用,從而進(jìn)一步實(shí)現(xiàn)線程的通信。
其中,wait()方法會(huì)釋放占有的對(duì)象鎖,當(dāng)前線程進(jìn)入等待池,釋放cpu,而其他正在等待的線程即可搶占此鎖,獲得鎖的線程即可運(yùn)行程序;線程的sleep()方法則表示,當(dāng)前線程會(huì)休眠一段時(shí)間,休眠期間,會(huì)暫時(shí)釋放cpu,但并不釋放對(duì)象鎖,也就是說,在休眠期間,其他線程依然無法進(jìn)入被同步保護(hù)的代碼內(nèi)部,當(dāng)前線程休眠結(jié)束時(shí),會(huì)重新獲得cpu執(zhí)行權(quán),從而執(zhí)行被同步保護(hù)的代碼。
wait()和sleep()最大的不同在于wait()會(huì)釋放對(duì)象鎖,而sleep()不會(huì)釋放對(duì)象鎖。
notify()方法會(huì)喚醒因?yàn)檎{(diào)用對(duì)象的wait()而處于等待狀態(tài)的線程,從而使得該線程有機(jī)會(huì)獲取對(duì)象鎖。調(diào)用notify()后,當(dāng)前線程并不會(huì)立即釋放鎖,而是繼續(xù)執(zhí)行當(dāng)前代碼,直到synchronized中的代碼全部執(zhí)行完畢,才會(huì)釋放對(duì)象鎖。JVM會(huì)在等待的線程中調(diào)度一個(gè)線程去獲得對(duì)象鎖,執(zhí)行代碼。
需要注意的是,wait()和notify()必須在synchronized代碼塊中調(diào)用。
notifyAll()是喚醒所有等待的線程。
?
下面是示例代碼,
package?com.bpan.spring.beans.thread;
public?class?ThreadDemo?{
????
????static?final?Object?obj?=?new?Object();
????
????//第一個(gè)子線程
????static?class?ThreadA?implements?Runnable{
????????@Override
????????public?void?run()?{
????????????
????????????
????????????int?count?=?10;
????????????while(count?>?0)?{
????????????????
????????????????synchronized?(ThreadDemo.obj)?{
????????????????????
????????????????????System.out.println("A-----"+count);
????????????????????count--;
????????????????????
????????????????????synchronized?(ThreadDemo.obj)?{
????????????????????????
????????????????????????//notify()方法會(huì)喚醒因?yàn)檎{(diào)用對(duì)象的wait()而處于等待狀態(tài)的線程,從而使得該線程有機(jī)會(huì)獲取對(duì)象鎖。
????????????????????????//調(diào)用notify()后,當(dāng)前線程并不會(huì)立即釋放鎖,而是繼續(xù)執(zhí)行當(dāng)前代碼,直到synchronized中的代碼全部執(zhí)行完畢,
????????????????????????ThreadDemo.obj.notify();
????????????????????????
????????????????????????try?{
????????????????????????????ThreadDemo.obj.wait();
????????????????????????}?catch?(InterruptedException?e)?{
????????????????????????????//?TODO?Auto-generated?catch?block
????????????????????????????e.printStackTrace();
????????????????????????}
????????????????????}
????????????????}
????????????}
????????????
????????}
????????
????}
????
????static?class?ThreadB?implements?Runnable{
????????
????????
????????@Override
????????public?void?run()?{
????????????
????????????int?count?=?10;
????????????
????????????while(count?>?0)?{
????????????????
????????????????synchronized?(ThreadDemo.obj)?{
????????????????????System.out.println("B-----"+count);
????????????????????count--;
????????????????????
????????????????????synchronized?(ThreadDemo.obj)?{
????????????????????
????????????????????????//notify()方法會(huì)喚醒因?yàn)檎{(diào)用對(duì)象的wait()而處于等待狀態(tài)的線程,從而使得該線程有機(jī)會(huì)獲取對(duì)象鎖。
????????????????????????//調(diào)用notify()后,當(dāng)前線程并不會(huì)立即釋放鎖,而是繼續(xù)執(zhí)行當(dāng)前代碼,直到synchronized中的代碼全部執(zhí)行完畢,
????????????????????????ThreadDemo.obj.notify();
????????????????????????
????????????????????????try?{
????????????????????????????ThreadDemo.obj.wait();
????????????????????????}?catch?(InterruptedException?e)?{
????????????????????????????//?TODO?Auto-generated?catch?block
????????????????????????????e.printStackTrace();
????????????????????????}
????????????????????}
????????????????????
????????????????}
????????????????
????????????}
????????????
????????}
????????
????}
????
????public?static?void?main(String[]?args)?{
????????
????????
????????new?Thread(new?ThreadA()).start();
????????new?Thread(new?ThreadB()).start();
????????
????}
}
粉絲福利:Java從入門到入土學(xué)習(xí)路線圖
???

?長按上方微信二維碼?2 秒
感謝點(diǎn)贊支持下哈?
