Java中關(guān)于線程安全的三種解決方式
三個(gè)窗口賣票的例子解決線程安全問(wèn)題
- 問(wèn)題:買票過(guò)程中,出現(xiàn)了重票、錯(cuò)票-->出現(xiàn)了線程的安全問(wèn)題
- 問(wèn)題出現(xiàn)的原因:當(dāng)某個(gè)線程操作車票的過(guò)程中,尚未操作完成時(shí),其他線程參與進(jìn)來(lái),也操作車票
- 如何解決:當(dāng)一個(gè)線程a在操作ticket的時(shí)候,其他線程不能參與進(jìn)來(lái),知道線程a操作完ticket時(shí),其他線程才可以開(kāi)始操作ticket,這種情況即使線程a出現(xiàn)了阻塞,也不能被改變
- 在Java中,我們通過(guò)同步機(jī)制,來(lái)解決線程的安全問(wèn)題。(線程安全問(wèn)題的前提:有共享數(shù)據(jù))
方式一:同步代碼塊
synchronized(同步監(jiān)視器){
//需要被同步的代碼(或操作共享數(shù)據(jù)的代碼)
}
說(shuō)明:
1.操作共享數(shù)據(jù)的代碼,即為需要被同步的代碼(不能包含代碼多了(變成單線程會(huì)效率低,也有可能會(huì)出錯(cuò)),也不能包含代碼少了(沒(méi)包的會(huì)阻塞))
2.共享數(shù)據(jù):多個(gè)線程共同操作的變量。比如:ticket就是共享數(shù)據(jù)
3.同步監(jiān)視器,俗稱:鎖。任何一個(gè)類的對(duì)象,都可以充當(dāng)鎖。
要求:多個(gè)線程必須要共用同一把鎖。(特別注意?。。。。?
補(bǔ)充:在實(shí)現(xiàn)Runnable接口創(chuàng)建多線程的方式中,我們可以考慮使用(具體問(wèn)題具體分析)this充當(dāng)同步監(jiān)視器
class window1 implements Runnable{ private int ticket = 100; // Object obj = new Object(); @Override public void run() { while (true){ synchronized (this) {//此時(shí)的this:唯一的Window1的對(duì)象 // synchronized(obj) { if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "賣票,票號(hào)為:" + ticket); ticket--; } else { break; } } } } } public class WindowTest1 { public static void main(String[] args) { window1 w = new window1();//只造了一個(gè)對(duì)象,所以100張票共享 Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("線程1"); t2.setName("線程2"); t3.setName("線程3"); t1.start(); t2.start(); t3.start(); } }
class Window extends Thread{ private static int ticket = 100;//三個(gè)窗口共享:聲明為static private static Object obj = new Object(); @Override public void run() { while(true) { // synchronized (obj) { synchronized (Window.class){//Class clazz = Window.class,Window.class只會(huì)加載一次 // synchronized (this) {//錯(cuò)誤的方式:this代表著t1,t2,t3三個(gè)對(duì)象 if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName() + ":賣票,票號(hào)為:" + ticket); ticket--; } else { break; } } } } } public class WindowTest { public static void main(String[] args) { Window t1 = new Window(); Window t2 = new Window(); Window t3 = new Window(); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
方式二:同步方法
如果操作共享數(shù)據(jù)的代碼完整的聲明在一個(gè)方法中,我們不妨將此方法聲明同步的。
4.同步的方式,解決了線程的安全問(wèn)題。---->好處
操作同步代碼時(shí),只能有一個(gè)線程參與,其他線程等待。相當(dāng)于是一個(gè)單線程的過(guò)程,效率低。--->局限性
關(guān)于同步方法的總結(jié):
1.同步方法仍然涉及到同步監(jiān)視器,只是不需要我們顯式的聲明。
2.非靜態(tài)的同步方法,同步監(jiān)視器是:this
靜態(tài)的同步方法,同步監(jiān)視器是:當(dāng)前類本身
class window3 implements Runnable{ private int ticket = 100; @Override public void run() { while (true){ show(); } } public synchronized void show(){//同步監(jiān)視器:this(未顯示聲明而已) if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "賣票,票號(hào)為:" + ticket); ticket--; } } } public class WindowTest3 { public static void main(String[] args) { window3 w = new window3(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("線程1"); t2.setName("線程2"); t3.setName("線程3"); t1.start(); t2.start(); t3.start(); } }
class Window4 extends Thread{ private static int ticket = 100;//三個(gè)窗口共享:聲明為static @Override public void run() { while(true){ show(); } } private static synchronized void show() {//同步監(jiān)視器:Window4.class(類) // private synchronized void show() {//同步監(jiān)視器:t1,t2,t3。此種解決方式是錯(cuò)誤的 if(ticket > 0){ System.out.println(Thread.currentThread().getName() + ":賣票,票號(hào)為:" + ticket); ticket--; } } } public class WindowTest4 { public static void main(String[] args) { Window4 t1 = new Window4(); Window4 t2 = new Window4(); Window4 t3 = new Window4(); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
方式三:Lock鎖---JDK5.0新增
JDK5.0開(kāi)始,Java提供了更強(qiáng)大的線程同步機(jī)制---通過(guò)顯式定義同步鎖對(duì)象來(lái)實(shí)現(xiàn)同步,同步鎖使用Lock對(duì)象充當(dāng)。
java.util.concurrent.locks接口是控制多個(gè)線程對(duì)共享資源進(jìn)行訪問(wèn)的工具。鎖提供了對(duì)共享資源的獨(dú)占訪問(wèn),每次只能有一個(gè)線程對(duì)Lock對(duì)象加鎖,線程開(kāi)始訪問(wèn)共享資源之前應(yīng)先獲得Lock對(duì)象
ReentrantLock類實(shí)現(xiàn)了Lock,它擁有與synchronized相同的并發(fā)性和內(nèi)存語(yǔ)義,在實(shí)現(xiàn)線程安全的控制中,比較常用的是ReentrantLock,可以顯式加鎖,釋放鎖。
class Window implements Runnable{ private int ticket = 100; //1.實(shí)例化ReentrantLock private ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (true){ try{ //2.調(diào)用鎖定方法:lock() lock.lock(); if(ticket > 0){ try { Thread.sleep(100); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "售票,票號(hào)為:" + ticket); ticket--; }else{ break; } } finally{ //3.調(diào)用解鎖方法:unlock(); lock.unlock(); } } } } public class LockTest { public static void main(String[] args) { Window w = new Window(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
線程的死鎖問(wèn)題
1.死鎖的理解:不同的線程分別占用對(duì)方需要的同步資源不放棄,都在等待對(duì)方放棄自己的需要
的同步資源,就形成了線程的死鎖。
2.說(shuō)明:
>出現(xiàn)死鎖后,不會(huì)出現(xiàn)異常,不會(huì)出現(xiàn)提示,只是所有的線程都處于阻塞狀態(tài),無(wú)法繼續(xù)
>我們使用同步時(shí),要避免出現(xiàn)死鎖
3.解決方法
A.專門的算法、原則 B.盡量減少同步資源的定義 C.盡量避免嵌套同步
public class ThreadTest { public static void main(String[] args) { StringBuffer s1 = new StringBuffer(); StringBuffer s2 = new StringBuffer(); new Thread(){//匿名的方式繼承 @Override public void run() { synchronized(s1){ s1.append("a"); s2.append("1"); try { Thread.sleep(100);//增加死鎖出現(xiàn)概率 } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s2){ s1.append("b"); s2.append("2"); System.out.println(s1); System.out.println(s2); } } } }.start(); new Thread(new Runnable(){//匿名的方式實(shí)現(xiàn)Runnable接口 @Override public void run() { synchronized (s2){ s1.append("c"); s2.append("3"); try { Thread.sleep(100);//增加死鎖出現(xiàn)概率 } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s1){ s1.append("d"); s2.append("4"); System.out.println(s1); System.out.println(s2); } } } }).start(); } }
synchronized和Lock的對(duì)比
1.Lock是顯示鎖(手動(dòng)開(kāi)啟和關(guān)閉鎖,別忘記關(guān)閉鎖),synchronized是隱式鎖,出了作用域自動(dòng)釋放
2.Lock只有代碼塊鎖,synchronized有代碼塊鎖和方法鎖
3.使用Lock鎖,JVM將花費(fèi)較少的時(shí)間來(lái)調(diào)度線程,性能更好。并且具有更好的拓展性(提供更多的子類)
4.synchronized 與 Lock的異同?
相同:二者都可以解決線程安全問(wèn)題
不同:synchronized機(jī)制在執(zhí)行完相應(yīng)的同步代碼以后,自動(dòng)的釋放同步監(jiān)視器 Lock需要手動(dòng)的啟動(dòng)同步(lock()),同時(shí)結(jié)束同步也需要手動(dòng)的實(shí)現(xiàn)(unlock())
優(yōu)先使用順序: Lock ->同步代碼塊(已經(jīng)進(jìn)入了方法體,分配了相應(yīng)資源) ->同步方法(在方法體之外)
到此這篇關(guān)于Java中關(guān)于線程安全的三種解決方式的文章就介紹到這了,更多相關(guān)Java 線程安全內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java如何使用poi生成簡(jiǎn)單word文檔并導(dǎo)出
這篇文章主要介紹了Java如何使用poi生成簡(jiǎn)單word文檔并導(dǎo)出問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06java 二進(jìn)制數(shù)據(jù)與16進(jìn)制字符串相互轉(zhuǎn)化方法
今天小編就為大家分享一篇java 二進(jìn)制數(shù)據(jù)與16進(jìn)制字符串相互轉(zhuǎn)化方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-07-07Springboot應(yīng)用中線程池配置詳細(xì)教程(最新2021版)
這篇文章主要介紹了Springboot應(yīng)用中線程池配置教程(2021版),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03IntelliJ IDEA中折疊所有Java代碼,再也不怕大段的代碼了
今天小編就為大家分享一篇關(guān)于IntelliJ IDEA中折疊所有Java代碼,再也不怕大段的代碼了,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-10-10mybatis中<if>標(biāo)簽bool值類型為false判斷方法
這篇文章主要給大家介紹了關(guān)于mybatis中<if>標(biāo)簽bool值類型為false判斷方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用mybatis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08Spring Boot整合FTPClient線程池的實(shí)現(xiàn)示例
這篇文章主要介紹了Spring Boot整合FTPClient線程池的實(shí)現(xiàn)示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-12-12使用spring-boot-admin對(duì)spring-boot服務(wù)進(jìn)行監(jiān)控的實(shí)現(xiàn)方法
這篇文章主要介紹了使用spring-boot-admin對(duì)spring-boot服務(wù)進(jìn)行監(jiān)控的實(shí)現(xiàn)方法,需要的朋友可以參考下2018-02-02Spring中事務(wù)管理方案和事務(wù)管理器及事務(wù)控制的API詳解
這篇文章主要介紹了Spring中事務(wù)管理方案和事務(wù)管理器及事務(wù)控制的API詳解,事務(wù)管理是指對(duì)事務(wù)進(jìn)行管理和控制,以確保事務(wù)的正確性和完整性,事務(wù)管理的作用是保證數(shù)據(jù)庫(kù)的數(shù)據(jù)操作的一致性和可靠性,需要的朋友可以參考下2023-08-08