目錄
1.多線程運行的安全問題
2.多線程同步代碼塊
3.同步方法的鎖是this
4.靜態同步方法的鎖是Class對象
5.單例設計模式(面試中的考點)
6.死鎖(一個發生死鎖的例子)
多線程運行的安全問題
例子:售票系統
1 class Ticket implements Runnable 2 { 3 //定義靜態變量ticket
4 private static int ticket=100; 5 public void run() 6 { 7 while(true) 8 { 9 //判斷票編號是否大於0
10 if(ticket>0) 11 { 12 try
13 { 14 Thread.sleep(10); 15 } 16 catch (InterruptedException ie) 17 { 18 System.err.println("Error:"+ie); 19 } 20 System.out.println(Thread.currentThread().getName()+"--sale:"+ticket--); 21 } 22 } 23 } 24
25 } 26 public class ThreadDemo 27 { 28
29 public static void main(String[] args) 30 { 31 //創建一個實現了Runnable接口的Ticket對象
32 Ticket t=new Ticket(); 33 //創建4個線程
34 Thread t1=new Thread(t); 35 Thread t2=new Thread(t); 36 Thread t3=new Thread(t); 37 Thread t4=new Thread(t); 38 //分別啟動4個線程
39 t1.start(); 40 t2.start(); 41 t3.start(); 42 t4.start(); 43
44 } 45 }
運行:
最后,打印出了了“0,-1,-2”的錯票情況(票的編號從1-100).多線程出現安全問題。
問題的原因:
當多條語句在操作用一個線程共享數據時,一個線程對多條語句只執行了一部分,還沒執行完,
另一個數據參與進來執行,造成共享數據的錯誤。
解決方法:
對多條操作共享數據的語句,只能讓一個線程都執行完,在執行過程中不允許其他線程參與進來。
java對於多線程的同步提供了專業的解決方法,就是同步代碼塊。
多線程同步代碼塊
synchronized(對象)
{需要被同步的代碼塊}
對象如同鎖,持有鎖的線程可以在同步代碼塊中執行,
沒有持有鎖的線程即使獲得CPU的執行權也進不去,因為沒有獲取鎖。
例子:火車上的衛生間。
同步的前提:
1.必須要有兩個或者兩個以上的線程。
2.必須是多個線程使用同一把鎖。
好處:解決了多線程的安全問題。
弊端:需要判斷鎖,較為消耗資源。
一般不可以把run()方法全放在同步代碼塊中,否則就是單線程了。
同步方法:
銀行
有兩個儲戶,分別存300元,每次存100元,存3次
目的:該線程是否有安全問題,如果有,如何解決?
如何找問題:
1.明確哪些代碼是多線程運行代碼
2.明確共享數據
3.明確多線程運行代碼中哪些代碼操作共享數據的。
1 class Bank 2 { 3 //定義sum,代表銀行的總金額
4 private int sum; 5 Object obj=new Object(); 6 public void add(int n) 7 { 8
9 //sum為共享數據,對sum有兩句操作, 10 //防止出現不安全問題,使用同步代碼塊
11 synchronized(obj) 12 { 13 sum=sum+n; 14 try
15 { 16 Thread.sleep(10); 17 } 18 catch (InterruptedException ie) 19 { 20 System.err.println("Error:"+ie); 21 } 22 System.out.println("sum="+sum); 23 } 24 } 25 } 26 class Cus implements Runnable 27 { 28 private Bank b=new Bank(); 29 //存三次
30 public void run() 31 { 32 for(int i=0;i<3;i++) 33 b.add(100); 34 } 35 } 36 public class BankDemo 37 { 38 public static void main(String[] args) 39 { 40 Cus cus=new Cus(); 41 //創建兩個線程,代表2個儲戶的存錢過程
42 Thread t1=new Thread(cus); 43 Thread t2=new Thread(cus); 44 t1.start(); 45 t2.start(); 46 } 47 }
注:同步的兩種表現形式:a.同步代碼塊 b.同步函數
同步方法的鎖是this
同步方法用的是哪一個鎖呢?
方法需要被對象調用,那么方法都有一個所屬對象的引用,就是this。
所以同步方法的鎖就是this。
靜態同步方法的鎖是Class對象
當同步方法被靜態修飾后,使用的鎖就不是this了,因為靜態方法中不可以使用this。
靜態方法進內存時,內存沒有本類對象,但一定有該類對應的字節碼文件對象。
類名.class。該對象的類型是Class。
靜態同步方法,使用的鎖是該方法所在的類的字節碼文件對象。類名.class
單例設計模式
1.餓漢式
1 class Single 2 { 3 private static final Single s=new Single(); 4 private Single(){} 5 public static Single getInstance() 6 { 7 return s; 8 } 9 }
2.懶漢式:
1 /*
2 面試時的考點: 3
4 懶漢式的特點:實例的延遲加載 5 會出現的問題:多線程訪問時會出現安全問題 6 解決方法:用同步方法或者同步代碼塊都行, 7 但是有些低效,可以通過雙重判斷,減少判斷鎖的次數,稍微提高效率。 8 加同步時候:使用的鎖是該類的字節碼對象 9
10 */
11 class Single 12 { 13 private static Single s=null; 14 private Single(){} 15 public static Single getInstance() 16 { 17 //通過雙重判斷,減少判斷鎖的次數,稍微提高效率。
18 if (s==null) 19 { 20 synchronized (Single.class) 21 { 22 if(s==null) 23 s=new Single(); 24 } 25 } 26 return s; 27 } 28 }
死鎖:同步中嵌套同步
死鎖的例子
1 class Test implements Runnable 2 { 3 private boolean flag; 4 Test(boolean _flag) 5 { 6 flag=_flag; 7 } 8 public void run() 9 { 10 if(flag) 11 { 12 synchronized(MyLock.locka) 13 { 14 System.out.println("if locka"); 15 synchronized(MyLock.lockb) 16 { 17 System.out.println("if lockb"); 18
19 } 20
21 } 22
23 } 24 else
25 { 26 synchronized(MyLock.lockb) 27 { 28 System.out.println("else lockb"); 29 synchronized(MyLock.locka) 30 { 31 System.out.println("else locka"); 32
33 } 34
35 } 36
37 } 38
39 } 40 } 41 class MyLock 42 { 43 static Object locka=new Object(); 44 static Object lockb=new Object(); 45 } 46 public class DeadLockTest 47 { 48 public static void main(String[] args) 49 { 50 Thread t1=new Thread(new Test(true)); 51 Thread t2=new Thread(new Test(false)); 52 t1.start(); 53 t2.start(); 54
55 } 56
57 }