標籤:
------Java培訓、Android培訓、iOS培訓、期待與您交流! -------
安全問題產生的原因:
當多條代碼語句在操作同一個共用資料時,一個線程對多條語句只執行了一部分,還沒有執行完, 另一個線程參與進來執行。導致共用資料的錯誤。
class Mlpc implements Runnable{ private int ticket = 50; public void run() { while(true) { if(ticket>0) { try { Thread.sleep(20); //讓線程進入凍結狀態 } catch (Exception e) { } System.out.println(Thread.currentThread()+" "+ticket--); } } }}class mlpcDemo { public static void main(String[] args) { Mlpc m = new Mlpc(); Thread th0 = new Thread(m); Thread th1 = new Thread(m); Thread th2 = new Thread(m); Thread th3 = new Thread(m); th0.start(); th1.start(); th2.start(); th3.start(); }}
出現了票數為-1和-2這樣的結果。
解決辦法:
針對多條線程將會操作共用資料的那條語句,每次只能讓一個線程都執行完。在執行過程中,其他線程不可以參與執行。
方式:
同步代碼塊:
class Mlpc implements Runnable{ private int ticket = 50; Object obj = new Object(); public void run() { while(true) { synchronized (obj) { if(ticket>0) { try { Thread.sleep(20); } catch (Exception e) { } System.out.println(Thread.currentThread()+" "+ticket--); } } } }}
對象如同鎖,持有鎖的線程可以在同步中執行。非持有鎖的線程,即使擷取cpu的執行權,也進不去,因為沒有擷取鎖。
同步的前提:
1.必須要有兩個或者兩個以上的線程
2.必須多個線程必須使用同一個鎖。 必須保證同步中只能有一個線程在運行。
好處:解決了線程的安全問題
弊端:消耗了資源,多個線程需要判斷鎖。
兩個儲戶分別向銀行存入300元,每次100,存3次。
找出改程式的安全問題。
1,明確哪些代碼是多線程運行代碼。
2,明確共用資料
3,明確多線程運行代碼中哪些語句是操作共用資料的。
我們建立4個線程,與之前不同的是,同時使用了同步函數和同步代碼快。看看結果如何。
class Mlpc implements Runnable{ private int ticket = 550; public boolean flag = true; Object obj = new Object(); public void run() { if(flag == true) { while(true) { synchronized (obj) { if(ticket>0) { try { Thread.sleep(20); } catch (Exception e) { } System.out.println(Thread.currentThread()+" show "+ticket--); } } } } else { while(true) { show(); } } } public synchronized void show() { if(ticket>0) { try { Thread.sleep(20); } catch (Exception e) { } System.out.println(Thread.currentThread()+" show "+ticket--); } }} class mlpcDemo { public static void main(String[] args) { Mlpc m = new Mlpc(); Thread th0 = new Thread(m); Thread th1 = new Thread(m); Thread th2 = new Thread(m); Thread th3 = new Thread(m); th0.start(); try { Thread.sleep(20); } catch (Exception e) { } m.flag = false; th1.start(); th2.start(); th3.start(); }}
結果出現了票數為0。
public void run() { if(flag == true) { while(true) { synchronized (this) //從原本的任意對象改為函數自己所屬的對象this
{ if(ticket>0) { try { Thread.sleep(20); } catch (Exception e) { } System.out.println(Thread.currentThread()+" show "+ticket--); } } } } else { while(true) { show(); } } }
現在,無論如何都顯示正常結果。由此可以驗證出函數都有自己所屬的對象this,所以同步函數所使用的是this鎖。
死結:
當類中存在多個不同的鎖時,而這些鎖存在交叉,那麼就可能會出現死結
如:
1,鎖A--11
{鎖B--22}
2,鎖B--33
{鎖A--44}
像上面這樣出現交叉的鎖。如11 33 同時執行後 要運行22 44就鎖住了。22被下面B鎖了,44又被上面11鎖住了。那麼就執行不下去了。
class Demo implements Runnable{ private boolean flag; Demo(boolean flag) { this.flag = flag; } public void run() { if(flag) { while(true) synchronized(MyLock.locka) { System.out.println("if locka"); synchronized(MyLock.lockb) { System.out.println("if lockb"); } } } else { while (true) synchronized(MyLock.lockb) { System.out.println("else lockb"); synchronized(MyLock.locka) { System.out.println("else locka"); } } } }}class MyLock{ static Object locka = new Object(); static Object lockb = new Object();} class ThreadTest { public static void main(String[] args) { Thread t1 = new Thread(new Demo(true)); Thread t2 = new Thread(new Demo(false)); t1.start(); try { Thread.sleep(100); } catch (Exception e) { } t2.start(); }}
生產者與消費者問題:
1 class PCDemo 2 { 3 public static void main(String[] args) 4 { 5 Resource r = new Resource(); 6 Producer prd = new Producer(r); 7 Consumer csu = new Consumer(r); 8 9 Thread th0 = new Thread(prd);10 Thread th1 = new Thread(prd); 11 Thread th2 = new Thread(prd);12 Thread th3 = new Thread(prd);13 14 Thread th4 = new Thread(csu);15 Thread th5 = new Thread(csu);16 Thread th6 = new Thread(csu);17 Thread th7 = new Thread(csu);18 th0.start();19 th1.start();20 th2.start();21 th3.start();22 th4.start();23 th5.start();24 th6.start();25 th7.start();26 }27 }28 29 30 class Resource31 {32 private String name;33 private int count =1;34 private boolean flag = false;35 36 public synchronized void set(String name)37 {38 if(flag)39 try40 {41 wait();42 }43 catch (Exception e)44 {45 }46 this.name = name+"---"+count++;47 System.out.println(Thread.currentThread().getName()+"生產"+this.name);48 flag = true;49 this.notify();50 }51 public synchronized void out()52 {53 if(!flag)54 try55 {56 wait();57 }58 catch (Exception e)59 {60 }61 System.out.println(Thread.currentThread().getName()+"________消費"+this.name);62 flag = false;63 this.notify();64 }65 66 }67 68 class Producer implements Runnable69 {70 private Resource res;71 Producer(Resource res)72 {73 this.res = res;74 }75 public void run()76 {77 while(true)78 {79 res.set("物件");80 }81 }82 }83 84 class Consumer implements Runnable85 {86 private Resource res;87 Consumer(Resource res)88 {89 this.res = res;90 }91 public void run()92 {93 while(true)94 {95 res.out();96 }97 }98 }
如果只是開啟了兩個線程,運行時會輪流“消費--生產”很和諧。但如果兩者各開啟兩條線程,就會出現這樣的情況。
t1取得資格、執行權->生產商品1、將flag之位true後t1進入阻塞狀態、t2取得資格和執行權->進入阻塞狀態,t3取得資格和執行權->消費商品1->喚醒第一個進入線程池的t1、t3而後阻塞、t1取得資格,若同樣取得資格t4搶到執行權->t4阻塞->t1取得執行權->生產商品2->喚醒t2、t2取得執行權後不再判斷flag真假直接生產t3。
於是將if判斷改為while迴圈,這樣的確彌補了錯誤。但會導致t2"醒來"->判斷flag真假->t2進入阻塞狀態。造成了所有線程全部等待。於是使用notifyAll();總是喚醒全部線程。又一次解決了問題。
JDK1.5提供了多線程升級解決方案,將同步synchronized替換成現實lock操作,要為特定 Lock 執行個體獲得 Condition 執行個體,使用其 newCondition() 方法。
Condition 執行個體實質上被綁定到一個鎖上。Condition 將 Object 監視器方法(wait、notify 和 notifyAll)分解成截然不同的對象,
以便通過將這些對象與任意 Lock 實現組合使用。
黑馬程式員-學習日記(多安全執行緒問題和死結認識)