java多線程的難點是在:處理多個線程同步與并發運行時線程間的通信問題。java在處理線程同步時,常用方法有:
1、synchronized關鍵字。
2、Lock顯示加鎖。
3、信號量Semaphore。
線程同步問題引入:
創建一個銀行賬戶Account類,在創建并啟動100個線程往同一個Account類實例里面添加一塊錢。在沒有使用上面三種方法的情況下:
代碼:
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class AccountWithoutSync {PRivate static Account account = new Account(); //實例化一個賬戶public static void main(String[] args){long start = System.currentTimeMillis();//使用ExecutorService創建線程池ExecutorService executor = Executors.newCachedThreadPool();for(int i=0;i<100;i++){executor.execute(new AddPennyTask());}//關閉線程池 即使線程池中還有未完成的線程 返回未完成的清單executor.shutdown();//關閉之后還是要保證未完成的線程繼續完成 如果線程池中所有任務都完成了,isTerminated返回truewhile(!executor.isTerminated()){}long end = System.currentTimeMillis();//balance有余額的意思System.out.println("現在賬戶里面的余額是:" + account.getBalance());System.out.println("花費的時間以微秒為單位:"+(end-start)+"微秒");}//這個線程只調用了一個方法public static class AddPennyTask implements Runnable{@Overridepublic void run() {account.deposit(1);}}//一個內部類 用于 賬戶的相關處理public static class Account{private int balance =0;public int getBalance(){return balance;}public void deposit(int amount){int newBalance = balance + amount;//為了讓錯誤體現的更明顯try {Thread.sleep(4); //5毫秒} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}balance= newBalance;//其實就是balance +=amount;//不過換成這一段代碼結果就在100和99左右}}}
運行截圖
錯的不是一般的明顯,明明存入了100塊,顯示只有2塊,原因也很簡單,就是100個線程同時對acount進行修改,當第100該線程把錢改成100時,第2個線程最后修改,把它改成了2,這群不聽話的線程,那就好好管管他們吧,讓他們乖乖聽話,在這之前,還是要記住以下一些名詞:
競爭狀態:當多個線程訪問同一公共資源并引發沖突造成線程不同步,我們稱這種狀態為競爭狀態。
線程安全:是針對類來說的,如果一個類的對象在多線程程序中不會導致競爭狀態,我們就稱這類為線程安全的,上面的Account是線性不安全的,而又如StringBuffer是線程安全的,而StringBuilder則是線程不安全的。
臨界區:造成競爭狀態的原因是多個線程同時進入了程序中某一特定部分,如上面程序中的deposit方法,我們稱這部分為程序中的臨界區,我們要解決多線程不同步問題,就是要解決如何讓多個線程有序的訪問臨界區。
使用synchronized關鍵字
1、同步方法:在deposit方法前加synchronized關鍵字,使得該方法變成同步方法:public synchronized void deposit(double amount){}
2、同步語句:對某一代碼塊加synchronized關鍵字,常用格式:
synchronized(exper) {statement} 其中exper是對象的引用,如上面的程序,在要在調用depsoit方法時,改成這樣: synchronized(account){account.deposit(1);}
使用synchronized,其實是隱式地給方法或者代碼塊加了鎖,任何同步的實例方法都可以轉換為同步語句:
public synchronized void method(){}
轉換為同步語句: public void method{sysnchronized(this){}}
利用lock加鎖同步
java也可以用Lock顯示的對臨界區代碼加鎖以及解鎖,這比用synchronized關鍵字更加直觀靈活。
一個鎖是一個Lock接口的實例,該接口定義了加鎖解鎖的方法,且一個鎖可以多次調用其newCondition()方法創建名為Condition對象的實例,以此進行線程間的通信(在后面用到)。
有了Lock接口,我們還要實現它,java提供了RenentrantLock類,該類是為創建相互排斥鎖而實現了Lock接口,由此就好辦了,下面看一下書上的圖:

代碼如下:
public static class Account2{private static Lock lock = new ReentrantLock();private int balance =0;public int getBalance(){return balance;}public void deposit(int amount){lock.lock();try{int newBalance = balance + amount;Thread.sleep(4); balance= newBalance;}catch (InterruptedException e) {e.printStackTrace();}finally{lock.unlock();}}}
給個運行截圖:
100塊存入,且時間明顯比之前久了,100個線程都乖乖的排隊訪問臨界區。另外注意在對lock()的調用后,緊跟隨try catch finnaly語句,這是個好習慣。
利用信號量同步
信號量是個好東西,信號量機制在操作系統方面有著廣泛的應用,如linux進程同步信號量,而在java里,Semaphore包包含了一些訪問信號量的方法。
信號量可以用來限制訪問共享資源的線程數,在訪問臨界區資源前,線程必須獲取一個信號量,在訪問完之后返回一個信號量。下圖是關于類Semaphore,該類包含訪問信號量的方法:

利用信號量Semaphorre,可以將上面的Account類改成這樣:
public static class Account
{private static Semaphore semaphore = new Semaphore(1); //創建一個信號量private int balance =0;public int getBalance(){return balance;}public void deposit(int amount){try {semaphore.acquire();int newBalance = balance + amount;Thread.sleep(4); balance= newBalance;} catch (InterruptedException e) {e.printStackTrace();}finally{semaphore.release(); //返回一個信號量}}}
網上有很多關于java多線程的文章,有不少寫成系列的,但個人覺得像多線程這么有有意思的部分,還是得好好靜下心來品位,上面的三種方法,可以解決多線程同步問題,后面還會繼續學習,嗯,加油。
本文出自于博客園蘭幽,轉載請說明出處。
新聞熱點
疑難解答