1.線程間通信: 實(shí)際就是多個(gè)線程在操作同一資源,但是操作的動(dòng)作不同 要求: 有一個(gè)資源Res,成員變量為name和sex; 有兩個(gè)進(jìn)程input和output用來(lái)操作資源res,其中input用于設(shè)置資源,output用于取出資源。
package transportation;class Res{ String name; String sex;}class Input implements Runnable{ PRivate Res r; //采用帶參數(shù)的默認(rèn)構(gòu)造函數(shù),用于保證輸入和輸出進(jìn)程控制的是同一資源 Input(Res r){ this.r=r; } //如果將此函數(shù)變?yōu)橥胶瘮?shù),那么成為單線程函數(shù); //需要分析清楚那些需要采用同步,哪些不需要 public void run(){ int x=0; while(true){ //同步的參數(shù)為r,可以設(shè)置為Input.class或者output.class synchronized(r){ if(x%2==0){ r.name="mike"; r.sex="man"; } else { r.name="Lily"; r.sex="woman"; } x++; } } }}class Output implements Runnable{ private Res r; Output(Res r){ this.r=r; } public void run(){ while(true){ synchronized(r){ System.out.println(r.name+"..."+r.sex); } } }}public class Trans { public static void main(String[] args) { Res r=new Res();//一堆煤 即資源 Input i=new Input(r);//兩個(gè)大卡車,一進(jìn)一出 Output o=new Output(r); Thread t1=new Thread(i);//兩條公路 Thread t2=new Thread(o); t1.start(); t2.start(); }}此時(shí),出現(xiàn)的結(jié)果為:
理想結(jié)果應(yīng)該為mike和Lily相互交錯(cuò) 分析原因: 程序中沒(méi)有設(shè)置兩個(gè)進(jìn)程必須一前一后輪流交替運(yùn)行 2.解決方案:為資源設(shè)置一個(gè)成員變量flag,用于控制輸入和輸出進(jìn)程的執(zhí)行順序 代碼如下:
標(biāo)注: 1) notifyAll();喚醒所有進(jìn)程 notify( ) 喚醒一個(gè)進(jìn)程 wait( ) 使一個(gè)進(jìn)程進(jìn)入等待狀態(tài) 2)使用flag來(lái)控制線程的運(yùn)行, flag==false 進(jìn)程Input可以運(yùn)行 flag==true 進(jìn)行Output可以運(yùn)行 3) Wait和notify,notifyAll都使用同步中,因?yàn)橐獙?duì)持有監(jiān)視器(鎖)的線程操作 所以要使用在同步中,因?yàn)橹挥型讲胖挥墟i的概念
為什么這些操作線程的方法要定義在Object類中? 因?yàn)檫@些方法在操作同步中線程時(shí),都必須要標(biāo)識(shí)他們所操作的線程所獨(dú)有的鎖。只有同一個(gè)鎖上的被等待線程 可以被同一鎖的notify喚醒,不可以對(duì)不同鎖中的線程進(jìn)行喚醒。即等待和喚醒是同一把鎖 而鎖可以是任意對(duì)象,所以定義在Object類中 3.代碼優(yōu)化
class Res{ private String name; private String sex; private boolean flag=false; public synchronized void set(String name,String sex){ if(flag==true){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.name=name; this.sex=sex; flag=true; this.notify(); } public synchronized void out(){ if(flag==false) { try { this.wait(); } catch (Exception e) { } } System.out.println(name+"..."+sex); flag=false; this.notify(); }}class Input implements Runnable{ private Res r; //同步鎖在資源上,用來(lái)保證兩個(gè)同步函數(shù)使用同一個(gè)鎖 Input(Res r){ this.r=r; } //如果將此函數(shù)變?yōu)橥胶瘮?shù),那么成為單線程 public void run(){ int x=0; while(true){ if(x%2==0){ r.set("mike","man"); } else { r.set("Lily","Woman"); } x++; } }}class Output implements Runnable{ private Res r; Output(Res r){ this.r=r; } public void run(){ while(true){ r.out(); } }}public class Trans { public static void main(String[] args) { Res r=new Res();//一堆煤 new Thread(new Input(r)).start(); new Thread(new Output(r)).start(); }}1. 要求:輸入輸出打印編號(hào); 兩個(gè)進(jìn)程,一個(gè)負(fù)責(zé)輸入,一個(gè)負(fù)責(zé)輸出
package transportation;class Resource{ private String name; private int count=1; private boolean flag=false; public synchronized void set(String name){ if(flag==true){ try { this.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } this.name=name+"--"+count++; System.out.println(Thread.currentThread().getName()+"...生產(chǎn)者..."+this.name); flag=true; this.notify(); } public synchronized void out(){ if(!flag) { try { this.wait(); } catch (Exception e) { } } System.out.println(Thread.currentThread().getName()+"...消費(fèi)者..."+this.name); flag=false; this.notify(); }}class Producer implements Runnable{ private Resource res; Producer(Resource res){ this.res=res; } public void run() { while(true){ res.set("+product+"); } }}class Consumer implements Runnable{ private Resource res; Consumer(Resource res){ this.res=res; } public void run() { while(true){ res.out(); } }}public class PV { public static void main(String[] args) { Resource r=new Resource(); Producer pro=new Producer(r); Consumer con=new Consumer(r); Thread t1=new Thread(pro); Thread t2=new Thread(con); t1.start(); t2.start(); }}結(jié)果符合預(yù)期要求
2. 將主函數(shù)改為如下的代碼段來(lái)設(shè)置多個(gè)線程同時(shí)運(yùn)行
結(jié)果如下,可知出現(xiàn)問(wèn)題
原因: 并不是每次進(jìn)程被喚醒都會(huì)判斷標(biāo)記; notify喚醒的是線程池中的第一個(gè); 會(huì)導(dǎo)致數(shù)據(jù)混亂 3.改進(jìn)的代碼: 1)notify->notifyAll //喚醒所有的線程而非線程池中的第一個(gè)線程 if(flag)->while(flag)//每次線程被喚醒之后都需要判斷標(biāo)志位 if(!flag)->while(!flag) 2) 有多個(gè)線程時(shí), 判斷標(biāo)記位需要while,喚醒線程需要notifyAll
原因: 1.對(duì)于多個(gè)生產(chǎn)者和消費(fèi)者,為什么要定義while判斷標(biāo)記 原因:讓被喚醒的線程再一次判斷標(biāo)記 2.為什么定義notifyAll
java.util.concurrent.locks JDK1.5 提供了多線程升級(jí)解決方案。 將同步synchronized替換為lock操作,將object中的wait,notify,notifyAll,替換為condition對(duì)象。 API中的代碼
class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); return x; } finally { lock.unlock(); } } }生產(chǎn)者消費(fèi)者升級(jí)代碼JDK1.5 1)顯式鎖機(jī)制 2)顯示鎖上的等待喚醒機(jī)制 該實(shí)例中實(shí)現(xiàn)了只喚醒對(duì)方的操作.
import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;class Resource{ private String name; private int count=1; private boolean flag=false; //創(chuàng)建鎖和condition private Lock lock=new ReentrantLock(); private Condition condition_pro=lock.newCondition();//生產(chǎn)者 private Condition condition_con=lock.newCondition();//消費(fèi)者 public void set(String name) throws InterruptedException { lock.lock();//拿到鎖 try{ while(flag==true){ condition_pro.await(); //生產(chǎn)者等待 } this.name=name+"--"+count++; System.out.println(Thread.currentThread().getName()+"...生產(chǎn)者..."+this.name); flag=true; condition_con.signalAll();//消費(fèi)者喚醒 }finally{ lock.unlock();//釋放鎖 } } public void out() throws InterruptedException { lock.lock(); try{ while(!flag) condition_con.await();//消費(fèi)者等待 System.out.println(Thread.currentThread().getName()+"...消費(fèi)者..."+this.name); }finally{ lock.unlock(); } condition_pro.signalAll();//喚醒生產(chǎn)者 flag=false; }}class Producer implements Runnable{ private Resource res; Producer(Resource res){ this.res=res; } public void run() { while(true){ try{ res.set("+product+"); }catch(InterruptedException ex){ } } }}class Consumer implements Runnable{ private Resource res; Consumer(Resource res){ this.res=res; } public void run() { while(true){ try { res.out(); } catch (InterruptedException e) { e.printStackTrace(); } } }}public class PV { public static void main(String[] args) { Resource r=new Resource(); Producer pro=new Producer(r); Consumer con=new Consumer(r); Thread t1=new Thread(pro); Thread t2=new Thread(con); Thread t3=new Thread(pro); Thread t4=new Thread(con); t1.start(); t2.start(); t3.start(); t4.start(); }}1.停止線程 stop方法已經(jīng)過(guò)時(shí)。 如何停止線程? 只有一種方法:run方法運(yùn)行結(jié)束 開啟多線程運(yùn)行,運(yùn)行代碼通常是循環(huán)結(jié)構(gòu)。只要控制住循環(huán),就可以讓run方法運(yùn)行結(jié)束,也就是線程結(jié)束。
1)正常代碼:程序可以自然終止
package transportation;class StopThread implements Runnable{ boolean flag=true; public void run() { while(flag){System.out.println(Thread.currentThread().getName()+"....run"); } } public void changeFlag(){ flag=false; }}public class StopThreadDemo { public static void main(String[] args) { StopThread t=new StopThread(); Thread t1=new Thread(t); Thread t2=new Thread(t); t1.start(); t2.start(); int num=0; while(true){ if(num++==60){ t.changeFlag(); break; } System.out.println(Thread.currentThread().getName()+"...."+num); } }}2)異常代碼:線程沒(méi)有正常結(jié)束
package transportation;/*不是死循環(huán),代碼并沒(méi)有消耗資源;當(dāng)線程處于凍結(jié)狀態(tài),就不會(huì)讀取到標(biāo)記,則線程不會(huì)運(yùn)行*/class StopThread implements Runnable{ boolean flag=true; /*當(dāng)線程1或線程2開啟后,線程進(jìn)入后,wait()被鎖住*/ public synchronized void run() { while(flag){ try { wait(); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName()+"....Exception"); } System.out.println(Thread.currentThread().getName()+"....run"); } } public void changeFlag(){ flag=false; }}public class StopThreadDemo { public static void main(String[] args) { StopThread t=new StopThread(); Thread t1=new Thread(t); Thread t2=new Thread(t); t1.start(); t2.start(); int num=0; while(true){ if(num++==60){ t.changeFlag(); break; } System.out.println(Thread.currentThread().getName()+"...."+num); } }}結(jié)果為:
3)interrupt將處于凍結(jié)狀態(tài)的線程強(qiáng)制恢復(fù)為運(yùn)行狀態(tài)
運(yùn)行結(jié)果:
4)消除凍結(jié) class StopThread implements Runnable { boolean flag=true; public synchronized void run() { while(flag){ try { wait(); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName()+”….Exception”); // flag=false; } System.out.println(Thread.currentThread().getName()+”….run”); } } public void changeFlag(){ flag=false; } } public class StopThreadDemo { public static void main(String[] args) { StopThread t=new StopThread();
System.out.println(Thread.currentThread().getName()+”….”+num); } } } 4)
class StopThread implements Runnable{ boolean flag=true; public synchronized void run() { while(flag){ try { wait(); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName()+"....Exception"); flag=false; } System.out.println(Thread.currentThread().getName()+"....run"); } } public void changeFlag(){ flag=false; }}public class StopThreadDemo { public static void main(String[] args) { StopThread t=new StopThread(); Thread t1=new Thread(t); Thread t2=new Thread(t); t1.start(); t2.start(); int num=0; while(true){ if(num++==60){ //t.changeFlag(); //t1.interrupt(); //t2.interrupt(); break; }System.out.println(Thread.currentThread().getName()+"...."+num); } }}2.守護(hù)線程 setDaemon 1)public final void setDaemon(boolean on) 將該線程標(biāo)記為守護(hù)線程或用戶線程。當(dāng)正在運(yùn)行的線程都是守護(hù)線程時(shí),Java 虛擬機(jī)退出。 該方法必須在啟動(dòng)線程前調(diào)用。 2)將上面的主函數(shù)代碼變?yōu)橄旅娲a
public class StopThreadDemo { public static void main(String[] args) { StopThread t=new StopThread(); Thread t1=new Thread(t); Thread t2=new Thread(t); t1.setDaemon(true); t2.setDaemon(true); t1.start(); t2.start(); int num=0; while(true){ if(num++==60){ //t.changeFlag(); //t1.interrupt(); //t2.interrupt(); break; } System.out.println(Thread.currentThread().getName()+"...."+num); } System.out.println("over"); }}3)程序中有三個(gè)線程:主線程,開啟的t1,t2線程 t1,t2是守護(hù)線程;只要主函數(shù)結(jié)束,守護(hù)線程便結(jié)束 3.join public final void join() throws InterruptedException 等待該線程終止 拋出: InterruptedException - 如果任何線程中斷了當(dāng)前線程。當(dāng)拋出該異常時(shí),當(dāng)前線程的中斷狀態(tài)被清除。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注