前言:關(guān)于synchronized、wait、notify已經(jīng)notifyAll大家應(yīng)該不陌生,現(xiàn)在我大致說一下我的理解。
一:synchronized
synchronized中文解釋是同步,那么什么是同步呢,解釋就是程序中用于控制不同線程間操作發(fā)生相對(duì)順序的機(jī)制,通俗來講就是2點(diǎn),第一要有多線程,第二當(dāng)多個(gè)線程同時(shí)競(jìng)爭(zhēng)某個(gè)資源的時(shí)候會(huì)有先后順序。在java中有三種寫synchronized的方式
第一種:
寫在普通方法的前面,這種表示對(duì)實(shí)例對(duì)象加鎖。
第二種:
寫在靜態(tài)方法前面,這種表示對(duì)類對(duì)象加鎖
第三種:
寫在代碼塊中,鎖是Synchonized括號(hào)里配置的對(duì)象(可能是實(shí)例對(duì)象,也可能是類對(duì)象)
總體說來就2種,一種就是鎖實(shí)例對(duì)象,一種鎖類對(duì)象。
鎖實(shí)例對(duì)象就是當(dāng)多個(gè)線程同時(shí)操作這個(gè)實(shí)例對(duì)象的時(shí)候必須先獲取鎖,如果無法獲取鎖,則必須處于等待狀態(tài),而和鎖類對(duì)象區(qū)別是,當(dāng)多個(gè)線程同時(shí)操作的時(shí)候,任何以這個(gè)類對(duì)象實(shí)例化的對(duì)象都要獲取鎖才能操作。舉個(gè)簡(jiǎn)單例子
比如一個(gè)群人去打飯,只要是人就必須排隊(duì)等待,一個(gè)個(gè)的打飯。不管是誰,但是吃完飯之后把盤子送回原地,但是這個(gè)時(shí)候不同的人可能吃飯快慢不同,但是肯定先吃飯后送盤子。現(xiàn)在寫段代碼我們比對(duì)一下。
public class RunnableTest implements Runnable { private synchronized void testSyncMethod() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getId() + "testSyncMethod:" + i); } } public void run() { testSyncMethod(); } public static void main(String[] args) { ExecutorService exec = Executors.newFixedThreadPool(2); RunnableTest rt = new RunnableTest(); RunnableTest rt1 = new RunnableTest(); exec.execute(rt); exec.execute(rt1); exec.shutdown(); }
按照我們的理論輸出結(jié)果肯定是無序排列的。如圖
public class RunnableTest implements Runnable { private void testSyncBlock() { synchronized (RunnableTest.class) { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getId()+"testSyncBlock:" + i); } } } public void run() { testSyncBlock(); } public static void main(String[] args) { ExecutorService exec = Executors.newFixedThreadPool(2); RunnableTest rt = new RunnableTest(); RunnableTest rt1 = new RunnableTest(); exec.execute(rt); exec.execute(rt1); exec.shutdown(); } }
而這段代碼輸入結(jié)果肯定是有序的。如下
那么我們?cè)谒伎家粋€(gè)問題,如果類A有2個(gè)方法,如果我們?cè)谄渲幸粋€(gè)方法前面加入了synchronized,哪意味著我們別的線程調(diào)用這個(gè)類的另一個(gè)方法也需要獲取鎖才可以執(zhí)行,也是另一個(gè)方法只是讀,這樣一來性能就大大的降低,所以我們?cè)趯?shí)際開發(fā)中盡量少在方法前加入synchronized,那么我們應(yīng)該怎么做呢,既然是實(shí)際對(duì)象我們只需要加入一個(gè)類,鎖定此類,只需要讓類的一個(gè)方法進(jìn)行鎖定即可。ok下面代碼如下
public class A { private Object obj="123"; public void a(){ synchronized (obj) { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread() + "a:" + i); } } } public void b(){ for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread() + "b:" + i); } } }public class B implements Runnable{ private A a; public B(A a){ this.a=a; } public void run() { a.b(); } }public class C implements Runnable { private A a; public C(A a){ this.a=a; } public void run() { a.a(); } }public class E implements Runnable{ private A a; public E(A a){ this.a=a; } public void run() { a.a(); } }public class D { public static void main(String[] args) { A a=new A(); ExecutorService executorService= Executors.newCachedThreadPool(); executorService.execute(new E((a))); executorService.execute(new B(a)); executorService.execute(new C(a)); executorService.shutdown(); } }
按照我們理論這段代碼執(zhí)行順序是第一個(gè)線程和第二個(gè)線程無序,第三個(gè)線程必須等待第一個(gè)線程執(zhí)行完畢才可以,測(cè)試結(jié)果也論證了我們的理論如下
二:wait、notify已經(jīng)notifyAll
wait、notify、notifyAll是Object對(duì)象的屬性,并不屬于線程。我們先解釋這三個(gè)的一個(gè)很重要的概念
wait:使持有該對(duì)象的線程把該對(duì)象的控制權(quán)交出去,然后處于等待狀態(tài)(這句話很重要,也就是說當(dāng)調(diào)用wait的時(shí)候會(huì)釋放鎖并處于等待的狀態(tài))
notify:通知某個(gè)正在等待這個(gè)對(duì)象的控制權(quán)的線程可以繼續(xù)運(yùn)行(這個(gè)就是獲取鎖,使自己的程序開始執(zhí)行,最后通過notify同樣去釋放鎖,并喚醒正在等待的線程)
notifyAll:會(huì)通知所有等待這個(gè)對(duì)象控制權(quán)的線程繼續(xù)運(yùn)行(和上面一樣,只不過是喚醒所有等待的線程繼續(xù)執(zhí)行)
這個(gè)就好了,從上面的解釋我們可以看出通過wait和notify可以做線程之間的通信,當(dāng)A線程處理完畢通知B線程執(zhí)行,B線程執(zhí)行完畢以后A線程可以繼續(xù)執(zhí)行。ok我們使用例子來說明。
public class Temp { int count=0; public void waiter() throws InterruptedException { synchronized (this) { System.out.println("等待"); wait(); System.out.println(this.count); } } public void notifyer() throws InterruptedException { synchronized (this){ TimeUnit.SECONDS.sleep(1); System.out.println("喚醒"); for (int i=0;i<10;i++){ System.out.println(Thread.currentThread()+"notifyer:"+i); count+=i; } notify(); } }public class Waiter implements Runnable{ private Temp temp; public Waiter(Temp temp){ this.temp=temp; } public void run() { try { temp.waiter(); } catch (InterruptedException e) { e.printStackTrace(); } } }public class Notifyer implements Runnable{ private Temp temp; public Notifyer(Temp temp){ this.temp=temp; } public void run() { try { temp.notifyer(); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { Temp temp=new Temp(); ExecutorService executorService= Executors.newCachedThreadPool(); executorService.execute(new Waiter(temp)); executorService.execute(new Notifyer(temp)); executorService.shutdown(); }
其中在notify中加入休眠1s目的是讓線程waiter先執(zhí)行更能看明白
我們?cè)谂e一個(gè)例子,比如說我們經(jīng)常提到的客戶端請(qǐng)求和服務(wù)器響應(yīng),當(dāng)客戶端發(fā)送請(qǐng)求后就處于等待服務(wù)端的響應(yīng),而服務(wù)端會(huì)等待客戶端端請(qǐng)求然后響應(yīng)客戶端請(qǐng)求,下面我們看看怎么去寫代碼
首先我們寫一個(gè)對(duì)象Handler來專門處理客戶端和服務(wù)端的,對(duì)于客戶端有2個(gè)方法就是發(fā)送請(qǐng)求和等待服務(wù)端響應(yīng),對(duì)于服務(wù)端同樣2個(gè)方法那就是等待客戶端請(qǐng)求和響應(yīng)客戶端
public class Handler { private boolean isClientRequest=false; public void sendRequest(){ synchronized (this){ isClientRequest=true; this.notifyAll(); } } public void waitResponse() throws InterruptedException { synchronized (this){ while (isClientRequest){ this.wait(); } } } public void receiveRequest(){ synchronized (this) { isClientRequest = false; this.notifyAll(); } } public void waitRequest() throws InterruptedException { synchronized (this){ while (!isClientRequest){ this.wait(); } } } }
現(xiàn)在我們寫客戶端代碼,客戶端肯定先發(fā)送請(qǐng)求,但是先等待1s為了讓服務(wù)端處于等待的效果,發(fā)送請(qǐng)求后就處于等待狀態(tài)直到服務(wù)端的響應(yīng)
public class Client implements Runnable { private Handler handler; public Client(Handler handler) { this.handler = handler; } public void run() { try { while (!Thread.interrupted()) { System.out.println("客戶端發(fā)送請(qǐng)求"); TimeUnit.SECONDS.sleep(1); this.handler.sendRequest();//第二步 System.out.println("等待服務(wù)端的響應(yīng)"); this.handler.waitResponse();//第三步 } } catch (InterruptedException e) { } System.out.println("客戶端已經(jīng)完成請(qǐng)求"); }
然后我們寫服務(wù)端代碼,服務(wù)端首先處于等待狀態(tài),收到客戶端請(qǐng)求后立馬進(jìn)行處理,處理完畢之后再次等待客戶端的請(qǐng)求
public class Server implements Runnable { public Handler handler; public Server(Handler handler) { this.handler = handler; } public void run() { try { while (!Thread.interrupted()) { System.out.println("等待客戶端請(qǐng)求"); this.handler.waitRequest();//第一步 System.out.println("處理客戶端請(qǐng)求"); TimeUnit.SECONDS.sleep(1); this.handler.receiveRequest();//第四步 } } catch (InterruptedException e) { } System.out.println("服務(wù)端處理已經(jīng)完成"); } }
從上面我們預(yù)測(cè)肯定是等待客戶端請(qǐng)求,發(fā)送請(qǐng)求,等待響應(yīng),處理客戶端請(qǐng)求這樣循環(huán)的結(jié)果。如下
在說一下wait和sleep的區(qū)別
區(qū)別1:在wait期間對(duì)象鎖使釋放的
區(qū)別2:可以通過notify和notifyAll,或者玲命令到期,從wait中恢復(fù)執(zhí)行。如果wait不接受任何參數(shù),這種wait將無線的等待下去,直到線程收到notify或notifyall的消息
http://www.cnblogs.com/LipeiNet/p/6475851.html