本文發表于本人博客。
今天我來說說關于JAVA多線程知識,有錯誤請指出。大家都知道JAVA在服務端上處理也有很大優勢,很多公司也有在服務器跑JAVA進程,這說明JAVA在處理這個多線程以及并發下也有一定有優點的(這說法有點坑了)。下面來看看
那java中,不具備直接操作像操作系統一樣的PV信號,然而它提供了synchronized來實現同步機制,可能這樣說不夠嚴謹。JAVA的基類Object中有以下幾個方法:
public final native void notify();public final native void notifyAll();public final native void wait(long timeout) throws InterruptedException;
凡是繼承實現Object對象的都有這樣的方法,都有一個內存鎖:當有線程獲取改內存鎖之后其它線程就無法訪問改內存轉入等待,知道占用改內存鎖的線程釋放內存鎖才可以進入,這里網上好多地方也只是簡單的說了下,我也是現在才比較了解原來:第一次當有線程獲得了改對象的內存鎖時此線程就進入鎖池,其它需要使用該對象的線程進入等待池(即時調用了wait()).知道占用改對象內存鎖的線程調用notify()或者調用了notifyAll()釋放了,這里好像還有一個超時條件現在不知道回頭再看看!!那么系統就會從等待池中隨意挑選一個線程占用該對象的內存鎖進入鎖池,這里還要注意的是:Class也是一種對象,不是我們普通認為的new出來的才是對象,在這里它也屬于對象所以就出現了對整個對象加鎖、對整個class加鎖的情況了!
上面的3個方法必須與synchronized關鍵字一起使用,因為wait()以及notify()都是在已經獲得對象、類鎖的時候才可以操作,才能夠進入等待池以及釋放鎖。
春節將至,下面來個搶火車票的例子簡單說明一下,先看Train類:
class Train{ /** * 頭等高級坐席 */ PRivate int Tickets = 10; /** * 購買火車票 * @throws Exception */ public void Purchase(String name) throws Exception{// 模擬獲取Tickets數據操作 Thread.sleep(50); if(Tickets > 0){// 模擬獲取訂單數據等操作 Thread.sleep(10); int temp = Tickets--; System.out.println(name + "搶到:" + temp);// 模擬生成訂單等操作 Thread.sleep(10); } else{ System.out.println(name + "搶票失敗!"); } }}TrainThread類:
class TrainThread extends Thread{ private Train train; public TrainThread(Train train){ this.train = train; } @Override public void run() { try { this.train.Purchase(Thread.currentThread().getName()); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }}main函數:
public static void main(String[] args) { //春節搶購火車票例子,模擬100人搶購10張票 Train train = new Train(); TrainThread[] threads = new TrainThread[100]; for (int i = 0; i < 100; i++) { TrainThread th1 = new TrainThread(train); threads[i] = th1; } for (int i = 100 - 1; i >= 0; i--) { threads[i].start(); } }執行結果輸出:
Thread-99搶到:10Thread-97搶到:9Thread-81搶到:1Thread-79搶到:0Thread-25搶票失敗!Thread-27搶票失敗!Thread-38搶票失敗!Thread-98搶到:-1Thread-96搶到:-2Thread-40搶票失敗!Thread-84搶到:-3
不行啊,連-1的票都出來了完全沒實現要求,那分析下:應該是在if(Tickets > 0)里面模擬睡眠了10毫秒引起的,因為在睡眠10毫秒的時候很多線程也進來已經判斷了if(Tickets > 0),那既然分析了原因有什么方法解決啊,當然有,我們可以把這個代碼塊鎖起來,當一個線程正在判斷操作Tickets變量的時候不讓其它線程去訪問,那Java里面有個關鍵字synchronized,我們再來看看代碼:
public void Purchase(String name) throws Exception{// 模擬獲取Tickets數據操作 Thread.sleep(50); synchronized(this){ if(Tickets > 0){// 模擬獲取訂單數據等操作 Thread.sleep(10); int temp = Tickets--; System.out.println(name + "搶到:" + temp);// 模擬生成訂單等操作 Thread.sleep(10); } else{// System.out.println(name + "搶票失敗!"); } } }}現在再來看輸出:
Thread-93搶到:10Thread-5搶到:9Thread-3搶到:8Thread-1搶到:7Thread-4搶到:6Thread-2搶到:5Thread-0搶到:4Thread-13搶到:3Thread-18搶到:2Thread-16搶到:1
多運行幾次發現沒有重復也不會出現負數的情況了,正常!再來分析下,代碼用到synchronized(this):synchronized語句塊結束后會自動釋放鎖讓等待池的其中一個線程獲得該鎖。這還需要注意一下sleep以及notify方法的區別:它們2個最重要的區別是sleep釋放CPU控制權但是并為釋放內存鎖,而notify還釋放內存鎖。其實還有一個用法synchronized鎖住對象的方法,可以把代碼修改成如下:
public synchronized void Purchase(String name) throws Exception{// 模擬獲取Tickets數據操作 Thread.sleep(50); if(Tickets > 0){// 模擬獲取訂單數據等操作 Thread.sleep(10); int temp = Tickets--; System.out.println(name + "搶到:" + temp);// 模擬生成訂單等操作 Thread.sleep(10); } else{// System.out.println(name + "搶票失敗!"); } }
看到這里大家都會覺得這樣使用更簡單啊,是這樣是簡單了但是可能要考慮到的是性能問題,如果這個對象還是static的話那么久相當于synchronized(class)了,大大的降低系統運行速度,這個上面也說了,不單是對象有內存鎖,連類也是存在內存鎖的!
不是說實現多線程有2種方法,一是繼承Thread;二是實現接口Runnable。是的下面我們把Train改成TrainRun并實現接口Runnable,代碼如下:
class TrainRun implements Runnable{ /** * 頭等高級坐席 */ private int Tickets = 10; private Object obj = new Object(); /** * 購買火車票 * @throws Exception */ public synchronized void Purchase(String name) throws Exception{ Thread.sleep(50); if(Tickets > 0){ Thread.sleep(10); int temp = Tickets--; System.out.println(name + "搶到:" + temp); Thread.sleep(10); } else{} } @Override public void run() { try { this.Purchase(Thread.currentThread().getName()); } catch (Exception e) {e.printStackTrace();} }}main函數:
TrainRun train = new TrainRun(); Thread[] threads = new Thread[100]; for (int i = 0; i < 100; i++) { Thread th1 = new Thread(train); threads[i] = th1; } for (int i = 100 - 1; i >= 0; i--) { threads[i].start(); }輸出結果也是一樣達到要求:
Thread-99搶到:10Thread-0搶到:9Thread-2搶到:8Thread-4搶到:7Thread-6搶到:6Thread-8搶到:5Thread-10搶到:4Thread-12搶到:3Thread-14搶到:2Thread-16搶到:1
上面不是說了使用wait以及notify方法嗎,不錯說了,這3個主要用于線程間互相喚醒的功能,上面的例子中沒有這個需求互相喚醒共同協調完成一件事所以也就沒用,不過看下面的例子:
一個阿姨負責傳遞碗筷,每次傳遞一個,一個阿姨負責洗碗,每次洗一個,盆能最多容納5個碗,用代碼描述這2個阿姨的工作過程,先分析下,這里一個阿姨負責傳遞一個阿姨負責洗碗,要用到2個線程,而且是盆里的碗是共享使用的,傳遞一個增加一個,洗一個就減一個,洗碗的時候如果盆里沒碗則等待傳遞完成,如果傳遞的時候盆里的碗數量超過5則等待洗碗完成。看代碼:
Conf配置類:
class Conf{ public static int passCount = 2;//阿姨傳碗筷次數 public static int washCount = 2;//阿姨洗碗筷次數 public static int couter = 5; //盆做大容量5個}工作Work類:
class Work{ public int count = 0; /** * 洗碗筷 */ public synchronized void Wash(){ if (count < 1){ try { wait(); } catch (InterruptedException e) {e.printStackTrace();} } System.out.println("/t已經有碗筷:" + count + " 個"); System.out.println("/t/t正在洗碗筷:第" + count + "個"); count--; System.out.println("/t/t還剩下碗筷:" + count + "個"); notify(); } /** * 傳碗筷 */ public synchronized void Pass(){ if(count >= Conf.couter){ try { wait(); } catch (InterruptedException e) {e.printStackTrace();} } System.out.println("/t已經有碗筷:" + count + " 個"); System.out.println("/t/t再傳一個碗筷"); count++; System.out.println("/t/t傳送完畢"); notify(); }}傳送阿姨PassAunt類:
class PassAunt extends Thread{ private Work work ; public PassAunt(Work work){ this.work = work; } @Override public void run() { for (int i = 0; i < Conf.passCount; i++) { this.work.Pass(); try {Thread.sleep(50); } catch (InterruptedException e) {e.printStackTrace();} } }}洗碗阿姨WashAunt類:
class WashAunt extends Thread{ private Work work ; public WashAunt(Work work){ this.work = work; } @Override public void run() { for (int i = 0; i < Conf.washCount; i++) { this.work.Wash(); try {Thread.sleep(50); } catch (InterruptedException e) {e.printStackTrace();} } }}main函數:
public static void main(String[] args) { Work work = new Work(); PassAunt passThread = new PassAunt(work); WashAunt washThread = new WashAunt(work); passThread.start(); washThread.start(); }結果如下:
已經有碗筷:0 個 再傳一個碗筷 傳送完畢 已經有碗筷:1 個 正在洗碗筷:第1個 還剩下碗筷:0個 已經有碗筷:0 個 再傳一個碗筷 傳送完畢 已經有碗筷:1 個 正在洗碗筷:第1個 還剩下碗筷:0個
可以看到輸出傳遞2個洗2個,滿足條件。如果現在客人突然較多,老板娘也加入傳遞碗筷隊伍來了,那現在看調用:
public static void main(String[] args) { Work work = new Work(); PassAunt passThread = new PassAunt(work); PassAunt passThread01 = new PassAunt(work); WashAunt washThread = new WashAunt(work); passThread.start(); passThread01.start(); washThread.start(); }class Conf{ public static int passCount = 2;//阿姨傳碗筷次數 public static int washCount = 4;//阿姨洗碗筷次數 public static int couter = 5; //盆做大容量5個}運行多次輸出:
已經有碗筷:2 個 正在洗碗筷:第2個 還剩下碗筷:1個 已經有碗筷:1 個 正在洗碗筷:第1個 還剩下碗筷:0個
從這里看看到,我們增加了一個傳遞的阿姨并修改洗碗的次數washCount結果是正常。那么我們再來修改下passCount以為4及washCount為2來看看多一個阿姨洗碗結果又是怎么樣呢!
class Conf{ public static int passCount = 4;//阿姨傳送碗筷次數 public static int washCount = 2;//阿姨傳送碗筷次數 public static int couter = 5; //盆做大容量5個}
public static void main(String[] args) { Work work = new Work(); PassAunt passThread = new PassAunt(work); WashAunt washThread = new WashAunt(work); WashAunt washThread01 = new WashAunt(work); passThread.start(); washThread01.start(); washThread.start(); }結果輸出可能是:
已經有碗筷:0 個 正在洗碗筷:第0個 還剩下碗筷:-1個 已經有碗筷:-1 個 再傳一個碗筷 傳送完畢
奇怪竟然出現-1個碗,顯然是錯誤的,按道理1個傳遞碗筷的阿姨對應2個洗碗的阿姨數量是正確的。那現在看看這個洗碗的方法哪里有問題呢,當前線程調用了wait方法,再次醒來繼續往下執行,這個時候沒去判斷這個盆里還有多少碗,修改下使用while:
public synchronized void Wash(){ while (count < 1){ try { wait(); } catch (InterruptedException e) {e.printStackTrace();} } System.out.println("/t已經有碗筷:" + count + " 個"); System.out.println("/t/t正在洗碗筷:第" + count + "個"); count--; System.out.println("/t/t還剩下碗筷:" + count + "個"); notify(); }并把洗碗的次數隨便設置:
public static int washCount = 2;//阿姨傳送碗筷次數
再次運行看結果:
已經有碗筷:0 個 再傳一個碗筷 傳送完畢 已經有碗筷:1 個 正在洗碗筷:第1個 還剩下碗筷:0個
運行多次沒錯,最后洗碗全部洗完了!那洗碗是搞定了,傳遞碗筷會不會出現錯誤的情況呢,接下來看看同時增加一個傳遞碗筷的阿姨以及洗碗的阿姨,看代碼:
class Conf{ public static int passCount = 2;//阿姨傳送碗筷次數 public static int washCount = 2;//阿姨傳送碗筷次數 public static int couter = 5; //盆做大容量5個} public static void main(String[] args) { Work work = new Work(); PassAunt passThread = new PassAunt(work); PassAunt passThread01 = new PassAunt(work); WashAunt washThread = new WashAunt(work); WashAunt washThread01 = new WashAunt(work); passThread.start(); washThread01.start(); passThread01.start(); washThread.start(); }輸出結果:
已經有碗筷:0 個 再傳一個碗筷 傳送完畢 已經有碗筷:1 個 正在洗碗筷:第1個 還剩下碗筷:0個
再次把passCount為4以及washCount為2,看結果:
已經有碗筷:2 個 再傳一個碗筷 傳送完畢 已經有碗筷:3 個 再傳一個碗筷 傳送完畢
到最后還有碗筷并為洗完,分析一下這個是由于2個傳遞碗筷的阿姨總共傳送了8個碗筷,而2個阿姨洗碗的的總次數總共才是4,所以留下4個碗筷未洗,分析驗證結果留下4個是沒錯!那修改下其變量值:
public static int passCount = 4;//阿姨傳送碗筷次數 public static int washCount = 4;//阿姨傳送碗筷次數 public static int couter = 5; //盆做大容量5個
再看結果可能是:
已經有碗筷:2 個 正在洗碗筷:第2個 還剩下碗筷:1個 已經有碗筷:1 個 正在洗碗筷:第1個 還剩下碗筷:0個
多次運行最后洗碗,說明正確。到這里可能大家都發現了這個運行受到passCount以及washCount的影響,可以去掉適合洗碗阿姨有就洗,傳送的阿姨有萬就傳遞這樣的需求不,當然可以,看下面代碼:
修改配置Conf類
class Conf{ public static int couter = 5; //盆做大容量5個}再修改Work類:
class Work{ //上面跟之前一樣省略 /** * 傳碗筷 */ public synchronized void Pass(){ while(count >= Conf.couter){ try { wait(); } catch (InterruptedException e) {e.printStackTrace();} } System.out.println("/t已經有碗筷:" + count + " 個"); System.out.println("/t/t再傳一個碗筷"); count++; System.out.println("/t/t傳送完畢"); notify(); }}再修改PassAunt類:
class PassAunt extends Thread{ //上面跟之前一樣省略 @Override public void run() { while(true){ this.work.Pass(); try {Thread.sleep(50); } catch (InterruptedException e) {e.printStackTrace();} } }}再修改WashAunt類:
class WashAunt extends Thread{ //上面跟之前一樣省略 @Override public void run() { while (true) { this.work.Wash(); try {Thread.sleep(50); } catch (InterruptedException e) {e.printStackTrace();} } }}再修改下mian函數:
public static void main(String[] args) { Work work = new Work(); PassAunt passThread = new PassAunt(work); PassAunt passThread01 = new PassAunt(work); PassAunt passThread02 = new PassAunt(work); PassAunt passThread03 = new PassAunt(work); WashAunt washThread = new WashAunt(work); WashAunt washThread01 = new WashAunt(work); passThread.start(); washThread01.start(); passThread01.start(); passThread02.start(); passThread03.start(); washThread.start(); }其實就是把if修改成while,把for循環修改成while,多次運行看結果,顯示中不會出現負數以及超過5個碗筷的數量,完全正確。再次測試下,new多幾個傳送碗筷的阿姨或者洗碗的阿姨,查看結果也正常。
這次先到這里。堅持記錄點點滴滴!
新聞熱點
疑難解答