Java 多线程初探(二) - 通讯与协调
Java中線程共有5中狀態:
就緒隊列:處于就緒狀態的線程都在就緒隊列中等待CPU資源,而一般就緒隊列中會有多個線程。為此,系統會給每一個線程分配一個優先級,優先級高的可以排在較前面的位置,能優先得到CPU資源。對于優先級相同的線程,一般按照先來先服務的原則調度。
進程和線程最大的區別在于進程是由操作系統來控制的,而線程是由進程來控制的,所以是由程序員來控制的。進程都是相互獨立的,各自享有各自的內存空間。而一個進程中的多個線程是共享內存空間的,他們可以訪問相同的變量和對象。本來這樣的設計方便了線程間的通訊,但卻帶來了新的問題:多個線程同時訪問一個變量可能出現意想不到的錯誤,如死鎖。所以多線程操作要注意協調與配合,進行互斥和同步處理。
- 互斥:當多個線程需要訪問同一資源時,而這一資源在某一時刻只允許一個線程訪問,那么這些線程就是互斥。如線程A需要讀取變量count,而線程B會給count賦值,那邊A和B是互斥的。
- 同步:多個線程需要訪問同一資源,而且需要相互配合才能正常工作,這些線程運行時就是一種同步關系。例如,A線程需要從緩沖區中讀取數據,而B線程需要向緩沖區中寫數據,但緩沖區的大小是固定的。
- 臨界區:為了實現線程間的互斥和同步,需要將共享資源放入一個區域,該區域一次只允許一個線程進入,該區域就被稱為臨界區域。線程在訪問共享資源前需要進行檢查,看自己是否有權訪問資源,如果有權訪問還要阻止其他線程進入該區域。該代碼段就是臨界區。
- 死鎖:若多個線程相互等待其他線程釋放資源,而所有線程又都不釋放自己所占的資源,從而導致相關線程處于永遠等待的狀態,這種現象稱為線程死鎖。
線程通訊
線程間互斥和同步必須使用信號量,Java中信號量需要用戶自己管理,系統只提供了起到PV原語作用的方法:
- void notify():喚醒在此對象監視器上等待的某一個線程.具體是哪一個可以認為是不確定的.
- void notifyAll():喚醒在此對象監視器上等待的所有線程。
- void wait():導致當前的線程等待,直到其他線程調用此對象的 notify()方法或 notifyAll()方法.
wait會釋放占有的資源(鎖),notify和notifyAll不會釋放占用的資源(鎖).而線程睡眠(sleep)時,也都不會釋放資源(鎖)。
除了三個方法,還有一個常用的關鍵字:synchronized.
凡是被synchronized標志的資源,都是臨界區,在同一時刻只能有一個線程能夠進入該臨界區。synchronized可以用于數據、方法,甚至是一段代碼。synchronized也被稱為“對象鎖”,而且上面的三個方法都只能使用在由synchronized標志的代碼塊中,否則會發生如下異常:
java.lang.IllegalMonitorStateException: current thread not ownersynchronized是和一個鎖對象關聯的,一個對象只有一個鎖。當程序運行到synchronized同步方法或代碼塊時就會檢查鎖,如果一個線程獲得該鎖,就沒有其他線程可以獲得鎖,直到第一個線程釋放(或返回)鎖。這也意味著任何其他線程都不能進入該對象上的synchronized方法或代碼塊,直到該鎖被釋放。
如myThread.java文件代碼:
public class myThread extends Thread {private static int count=0;private static Object o=new Object();//定義一個Object對象,用于鎖 @Overridepublic void run() {synchronized(o){for(int i=0;i<20;i++){count+=1;System.out.println("My name is "+this.getName()+" count= "+count); try {sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}}}public myThread(String arg0) {super(arg0);} }啟動線程代碼如下:
myThread tf=new myThread("first"); myThread ts=new myThread("secod"); tf.start(); ts.start();執行的結果:
My name is secod count= 1 My name is secod count= 2 ..... ..... My name is secod count= 20 My name is first count= 21 My name is first count= 22 My name is first count= 23 ..... ..... My name is first count= 40myThread.java文件中的for循環使用synchronized標志,是臨界區,第一次進入的是secod線程,知道執行完畢釋放鎖,first線程才進入臨界區。
synchronized可以用于方法、數據,甚至是代碼塊:
- synchronized限制static方法的時候,鎖對象為這個類的class變量,相當于XXXClass.class. 例如: public static synchronized int setName(String name){Xxx.name = name; } 等價于: public static int setName(String name){synchronized(Xxx.class){Xxx.name = name;} }
- synchronized限制非static方法的時候,鎖對象為這個類的實例(相當于this).例如: public synchronized int getX() {return x++; } 等價于: public int getX() {synchronized (this) {return x;} }
- synchronized限制一個對象實例的時候,如(synchronized (xlock)),鎖對象為指定的這個對象實例,如xlock.
synchronized限制static方法的時候,在某一時刻,同一個虛擬機只能有一個線程正在執行這個static方法,因為鎖對象是這個class的XXXClass.class實例,一個虛擬機只有一個XXXClass.class實例
如果把myThread.java文件代碼改成如下:
public class myThread extends Thread {private static int count=0;private static Object o=new Object();//定義一個Object對象,用于鎖 @Overridepublic synchronized void run() {for(int i=0;i<20;i++){count+=1;System.out.println("My name is "+this.getName()+" count= "+count); try {sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}}public myThread(String arg0) {super(arg0);} }執行的結果:
My name is first count= 1 My name is secod count= 2 My name is secod count= 3 My name is first count= 4 My name is first count= 5 My name is secod count= 6 My name is secod count= 7 ..... ..... My name is secod count= 34 My name is first count= 35 My name is secod count= 36 My name is secod count= 37 My name is first count= 38 My name is first count= 39 My name is secod count= 40可見是交叉執行的,那是因為synchronized限制某一個類的非static方法的時候,對這個類的某一特定實例,在某一時刻,同一個虛擬機只能有一個線程正在執行這個方法,但是可以同時執行多個實例的這個特定方法,因為鎖對象不同.
轉載于:https://www.cnblogs.com/zhaiqianfeng/archive/2012/08/17/4617055.html
總結
以上是生活随笔為你收集整理的Java 多线程初探(二) - 通讯与协调的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: IOS UINavigationCont
- 下一篇: mvc HtmlHelper