標籤:線程同步 rgs ... 原因 方法 打電話 bsp public --
問題引入:線程的安全問題
以下程式發現出現0號票甚至負號票,原因都是由多線程操作共用資源saleTask所導致的安全執行緒問題。
1 class Example1 2 { 3 public static void main(String[] args) 4 { 5 SaleTask saleTask = new SaleTask(); //建立SaleTask對象 6 //建立四個線程並啟動 7 new Thread(saleTask,"視窗一").start(); 8 new Thread(saleTask,"視窗二").start(); 9 new Thread(saleTask,"視窗三").start();10 new Thread(saleTask,"視窗四").start();11 12 }13 }14 15 //定義SaleThread類實現Runnable介面16 class SaleTask implements Runnable17 {18 private int tickets = 10;19 public void run()20 {21 while(tickets>0){22 try{23 Thread.sleep(10); //次出讓線程休眠10毫秒,類比售票延遲。24 }catch(InterruptedException e){25 e.printStackTrace();26 }27 System.out.println(Thread.currentThread().getName()+"...sales..."+tickets--);28 }29 }30 }
問題解決:同步代碼塊或同步方法
安全執行緒問題其實是由於多個線程同時處理共用資源導致的。要解決這個問題,那麼必須保證用於處理共用資源的代碼在任何時刻都只能有一個線程在訪問。
為了實現這種限制,Java中提供了同步機制。當多個線程使用同一個共用資源時,可以將處理共用資源的代碼放在一個代碼塊中,使用synchronized關鍵字來修飾,被稱作同步代碼塊,文法格式如下:
synchronized (lock){ 操作共用資源的代碼塊}
上面的代碼中,lock是一個鎖對象,它是同步代碼塊的關鍵。當線程執行同步代碼塊時,首先會檢查鎖對象的標誌位,預設情況下標誌位為1,此線程會執行同步代碼塊,同時將鎖對象的標誌位置0。當一個新的線程執行到這段同步代碼塊時,由於鎖對象的標誌位是0,新線程會發生阻塞,等待當前線程執行完同步代碼塊後,鎖對象的標誌位被置為1,新線程才能進入同步代碼塊執行其中的代碼。迴圈往複,直到共用資源被處理完為止。這個過程好比一個公用電話亭,只有前面一個人打完電話出來以後,後面的人才能進去打電話。
下面用同步代碼塊改進程式:
1 class Example2 2 { 3 public static void main(String[] args) 4 { 5 Ticket1 ticket = new Ticket1(); 6 new Thread(ticket,"視窗一").start(); 7 new Thread(ticket,"視窗二").start(); 8 new Thread(ticket,"視窗三").start(); 9 new Thread(ticket,"視窗四").start();10 11 }12 }13 14 15 class Ticket1 implements Runnable16 {17 private int tickets = 10;18 Object lock = new Object(); //定義任意一個對象,作為同步代碼塊的鎖,必須放在run方法之外!19 public void run()20 {21 while(true){22 synchronized(lock){ //定義同步代碼塊23 try{24 Thread.sleep(10);25 }catch(InterruptedException e){26 e.printStackTrace();27 }28 if(tickets>0){29 System.out.println(Thread.currentThread().getName()+"...sales..."+tickets--);30 }else{31 break;32 }33 }34 }35 }36 }
注意:同步代碼塊中的鎖對象可以是任意類型的對象,但多個線程共用的鎖對象必須是唯一的。
同步方法
在方法前面同樣可以使用synchronized關鍵字來修飾,被修飾的方法為同步方法,它能實現和同步代碼塊同樣的功能。
被synchronized修飾的方法在某一時刻只允許一個線程訪問,訪問該方法的其他線程都會被阻塞,直到當前線程訪問完畢後,其他線程才有機會執行方法。
運用如下:
1 class Example3 2 { 3 public static void main(String[] args) 4 { 5 Ticket1 ticket = new Ticket1(); 6 new Thread(ticket,"視窗一").start(); 7 new Thread(ticket,"視窗二").start(); 8 new Thread(ticket,"視窗三").start(); 9 new Thread(ticket,"視窗四").start();10 11 }12 }13 14 15 class Ticket1 implements Runnable16 {17 private int tickets = 10;18 public void run()19 {20 while(true){21 saleTicket(); //調用售票方法22 if(tickets<=0){23 break;24 }25 }26 }27 28 //定義一個同步方法saleTicket()29 private synchronized void saleTicket()30 {31 if(tickets>0)32 {33 try{34 Thread.sleep(10);35 }catch(InterruptedException e){36 e.printStackTrace();37 }38 System.out.println(Thread.currentThread().getName()+"...sales..."+tickets--);39 }40 41 }42 } 43
同步方法的鎖就是當前調用該方法的對象,也就是this指向的對象。這樣做的好處是,同步方法被所有線程共用,方法所在的對象相對於所有線程來說是唯一的,從而保證了鎖的唯一性。當一個線程執行該方法時,其他線程就不能進入該方法中,直到這個線程執行完該方法為止,從而達到線程同步的效果。
靜態同步函數進記憶體的時候不存在對象,但存在其所屬類的位元組碼檔案對象,屬於Class類型的對象,所以靜態同步函數的鎖是其所屬類的位元組碼檔案對象,該對象可以直接用“類名.class”的方式擷取。
總結:
同步解決了多個線程同時訪問共用資料時的安全執行緒問題,只要加上同一個鎖,在同一時間內只能有一條線程執行。但線程在執行同步代碼時每次都要判斷鎖的狀態,非常消耗資源,效率較低。
多線程同步