一、使用同步解決買票問題
解決資源共享的同步操作問題,可以使用同步代碼塊和同步方法兩種方式。
第一種:使用同步代碼塊必須指定一個(gè)需要同步的對(duì)象,通常將當(dāng)前對(duì)象(this)設(shè)置成同步對(duì)象。
class SaleTask implements Runnable{ PRivate int ticket = 5;//剩余票數(shù) public void run() { if(ticket>0){ System.out.println("線程"+Thread.currentThread().getName()+"需要賣票"); synchronized (this) { if(ticket>0){ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("線程"+Thread.currentThread().getName()+ "賣了一張票,剩余票數(shù)"+(--ticket)+"張"); }else{ System.out.println("線程"+Thread.currentThread().getName()+"需要賣票,已無票"); } } }else{ System.out.println("線程"+Thread.currentThread().getName()+"需要賣票,已無票"); } }}public class Main { public static void main(String[] args){ SaleTask sale = new SaleTask(); for(int i=0;i<8;i++){ //有8個(gè)線程需要賣票 Thread t1 = new Thread(sale,"thread"+(i+1)); t1.start(); } }}運(yùn)行結(jié)果:
線程thread1需要賣票線程thread3需要賣票線程thread2需要賣票線程thread6需要賣票線程thread7需要賣票線程thread4需要賣票線程thread8需要賣票線程thread5需要賣票線程thread1賣了一張票,剩余票數(shù)4張線程thread5賣了一張票,剩余票數(shù)3張線程thread8賣了一張票,剩余票數(shù)2張線程thread4賣了一張票,剩余票數(shù)1張線程thread7賣了一張票,剩余票數(shù)0張線程thread6需要賣票,已無票線程thread2需要賣票,已無票線程thread3需要賣票,已無票第二種:同步方法 public synchronized void run() { if(ticket>0){ System.out.println("線程"+Thread.currentThread().getName()+ "需要賣票"); if(ticket>0){ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("線程"+Thread.currentThread().getName()+ "賣了一張票,剩余票數(shù)"+(--ticket)+"張"); }else{ System.out.println("線程"+Thread.currentThread().getName()+ "需要賣票,已無票"); } }else{ System.out.println("線程"+Thread.currentThread().getName()+ "需要賣票,已無票"); } }運(yùn)行結(jié)果:
線程thread1需要賣票線程thread1賣了一張票,剩余票數(shù)4張線程thread7需要賣票線程thread7賣了一張票,剩余票數(shù)3張線程thread3需要賣票線程thread3賣了一張票,剩余票數(shù)2張線程thread8需要賣票線程thread8賣了一張票,剩余票數(shù)1張線程thread4需要賣票線程thread4賣了一張票,剩余票數(shù)0張線程thread6需要賣票,已無票線程thread5需要賣票,已無票線程thread2需要賣票,已無票二、死鎖多個(gè)線程共享同一資源時(shí)需要進(jìn)行同步,以保證資源操作的完整性,但是過多的同步就有可能產(chǎn)生死鎖。
class ThreadTask implements Runnable{ private static String resourceA = "Resource A"; private static String resourceB = "Resource B"; public boolean flag;//標(biāo)記獲取資源的順序,true表示先獲取A,再獲取B;false則相反 public void run() { if(flag){ synchronized(resourceA){ System.out.println("線程"+Thread.currentThread().getName()+"獲取了資源A"); try{ Thread.sleep(500); }catch(InterruptedException e){ e.printStackTrace(); } synchronized(resourceB){ System.out.println("線程"+Thread.currentThread().getName()+"獲取了資源B"); } } } else{ synchronized(resourceB){ System.out.println("線程"+Thread.currentThread().getName()+"獲取了資源B"); try{ Thread.sleep(500); }catch(InterruptedException e){ e.printStackTrace(); } synchronized(resourceA){ System.out.println("線程"+Thread.currentThread().getName()+"獲取了資源A"); } } } }}public class Main { public static void main(String[] args){ ThreadTask task1 = new ThreadTask(); ThreadTask task2 = new ThreadTask(); task1.flag = true; task2.flag = false; Thread t1 = new Thread(task1,"thread-1"); Thread t2 = new Thread(task2,"thread-2"); t1.start(); t2.start(); }}運(yùn)行結(jié)果:
線程thread-1獲取了資源A線程thread-2獲取了資源B 兩個(gè)線程都在等待對(duì)方釋放手中的資源,這樣就產(chǎn)生了死鎖。三、線程操作案例——生產(chǎn)者與消費(fèi)者
線程操作中一個(gè)典型的案例——生產(chǎn)者與消費(fèi)者問題,生產(chǎn)者不斷生產(chǎn),消費(fèi)者不斷消費(fèi)生產(chǎn)者生產(chǎn)的產(chǎn)品。

從中可以看出,生產(chǎn)者產(chǎn)生資源后將資源放到一個(gè)區(qū)域中,消費(fèi)者從這個(gè)區(qū)域中取出產(chǎn)品,由于線程運(yùn)行的不確定性,可能會(huì)產(chǎn)生下面兩種問題:
1)生產(chǎn)者剛向數(shù)據(jù)存儲(chǔ)空間中添加了信息的名稱,還沒有添加信息的內(nèi)容,程序就切換到了消費(fèi)者線程,這樣消費(fèi)者線程讀取到的是更新后的信息的名稱和上一次信息的內(nèi)容。
2)生產(chǎn)者連續(xù)更新了多次信息,消費(fèi)者才開始讀取,或者,消費(fèi)者連續(xù)多次讀取信息,生產(chǎn)者卻還沒來及更新。
1.程序的基本實(shí)現(xiàn)
無論是生產(chǎn)者還是消費(fèi)者,操作的都是信息(資源),所以需要定義一個(gè)信息類。
//信息類(資源)class Info { private String name = "李興華"; //資源name private String content = "java講師"; //資源content public void setName(String name){ this.name = name; } public String getName(){ return name; } public void setContent(String content){ this.content = content; } public String getContent(){ return content; }}生產(chǎn)者和消費(fèi)者相當(dāng)于兩個(gè)線程,操作同一個(gè)空間,分別實(shí)現(xiàn)Runnable接口
//生產(chǎn)者線程class Producer implements Runnable{ private Info info = null; //資源的引用 public Producer(Info info){ this.info = info; } public void run() { boolean flag = false; for(int i=0;i<50;i++){ //循環(huán)50次讓生產(chǎn)者生產(chǎn)具體的內(nèi)容 if(flag){ this.info.setName("李興華"); try{ Thread.sleep(90); }catch(InterruptedException e){ e.printStackTrace(); } this.info.setContent("Java講師"); flag = false; }else{ this.info.setName("mldn"); try{ Thread.sleep(90); }catch(InterruptedException e){ e.printStackTrace(); } this.info.setContent("www.mldnjava.cn"); flag = true; } } }}//消費(fèi)者線程class Consumer implements Runnable{ private Info info = null; //資源的引用 public Consumer(Info info){ this.info = info; } public void run() { for(int i=0;i<50;i++){ try{ Thread.sleep(110); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(this.info.getName()+"--->"+this.info.getContent()); } }}主方法:public class Main { public static void main(String[] args){ Info i = new Info(); Producer p = new Producer(i); Consumer c = new Consumer(i); Thread pt = new Thread(p,"thread-1"); Thread ct = new Thread(c,"thread-2"); pt.start(); ct.start(); }}運(yùn)行結(jié)果:
李興華--->www.mldnjava.cnmldn--->Java講師李興華--->www.mldnjava.cnmldn--->Java講師mldn--->Java講師李興華--->www.mldnjava.cnmldn--->Java講師李興華--->www.mldnjava.cn李興華--->www.mldnjava.cnmldn--->Java講師......2.解決問題1——加入同步
將設(shè)置信息的方法和獲取信息的方法都設(shè)為同步方法,使得無論是設(shè)置信息還是讀取信息,都需要先獲取信息對(duì)象,在一段時(shí)間內(nèi),只有一個(gè)線程可以對(duì)資源進(jìn)行操作。
//信息類(資源)class Info { private String name = "李興華"; //資源name private String content = "Java講師"; //資源content public synchronized void set(String name,String content){ setName(name); try{ Thread.sleep(90); //加入延遲 }catch(InterruptedException e){ e.printStackTrace(); } setContent(content); } public synchronized void get(){ try{ Thread.sleep(100); //加入延遲 }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(getName()+"--->"+getContent()); } public void setName(String name){ this.name = name; } public String getName(){ return name; } public void setContent(String content){ this.content = content; } public String getContent(){ return content; }}生產(chǎn)者:
//生產(chǎn)者線程class Producer implements Runnable{ private Info info = null; //資源的引用 public Producer(Info info){ this.info = info; } public void run() { boolean flag = false; for(int i=0;i<50;i++){ if(flag){ this.info.set("李興華", "Java講師"); flag = false; }else{ this.info.set("mldn", "www.mldnjava.cn"); flag = true; } } }}消費(fèi)者:
//消費(fèi)者線程class Consumer implements Runnable{ private Info info = null; //資源的引用 public Consumer(Info info){ this.info = info; } public void run() { for(int i=0;i<50;i++){ this.info.get(); } }}mldn--->www.mldnjava.cn李興華--->Java講師mldn--->www.mldnjava.cnmldn--->www.mldnjava.cn李興華--->Java講師李興華--->Java講師李興華--->Java講師mldn--->www.mldnjava.cn李興華--->Java講師李興華--->Java講師......從運(yùn)行結(jié)果可以看出,信息錯(cuò)亂的問題解決了,但是仍然存在信息重復(fù)讀取的問題,既然有重復(fù)讀取,就有重復(fù)設(shè)置,對(duì)于這樣的問題需要用到Object類。
3.Object類對(duì)線程的支持
1.Object類有以下幾種方法是對(duì)線程支持的:

從表中可知,可以將線程設(shè)為等待狀態(tài),也可以喚醒線程。喚醒線程的方法有兩個(gè),所有等待的線程一般會(huì)按照順序排列,如果用notify()方法喚醒,則會(huì)喚醒第一個(gè)線程,若用notifyAll()方法喚醒,則會(huì)喚醒所有的等待線程,哪一個(gè)線程的優(yōu)先級(jí)高,哪一個(gè)線程就有可能先執(zhí)行。
2.解決問題2——加入等待與喚醒
如果想讓生產(chǎn)者不重復(fù)生產(chǎn),消費(fèi)者不重復(fù)讀取,可以增加一個(gè)標(biāo)志位,標(biāo)志位為boolean型,
若為true,表示生產(chǎn)者可以生產(chǎn),但消費(fèi)者不可以讀取,如果是消費(fèi)者線程則需要等待;
若為false,表示消費(fèi)者可以讀取,但生產(chǎn)者不可以生產(chǎn),如果是生產(chǎn)者線程則需要等待。

要完成以上功能,直接修改Info類即可,在Info類中增加一個(gè)標(biāo)志位,通過判斷標(biāo)志位完成等待與喚醒操作。
//信息類(資源)class Info { privateString name = "李興華"; //資源name privateString content = "Java講師"; //資源content privateboolean flag = false; //true,表示可以生產(chǎn),不能消費(fèi) //false表示可以消費(fèi),不能生產(chǎn) //生產(chǎn)資源 publicsynchronized void set(String name,String content){ if(!flag){ //為false時(shí),生產(chǎn)者線程等待 try{ super.wait(); //wait()方法會(huì)讓線程釋放手中的鎖 }catch (InterruptedException e) { e.printStackTrace(); } } setName(name); try{ Thread.sleep(90); //加入延遲 }catch(InterruptedExceptione){ e.printStackTrace(); } setContent(content); this.flag= false; super.notify(); //喚醒等待的線程 } //消費(fèi)資源 publicsynchronized void get(){ if(flag){ try{ super.wait(); }catch (InterruptedException e) { e.printStackTrace(); } } try{ Thread.sleep(100); //加入延遲 }catch(InterruptedExceptione){ e.printStackTrace(); } System.out.println(getName()+"--->"+getContent()); this.flag= true; super.notify(); //喚醒等待的線程 } publicvoid setName(String name){ this.name= name; } publicString getName(){ returnname; } publicvoid setContent(String content){ this.content= content; } publicString getContent(){ returncontent; }}運(yùn)行結(jié)果:
李興華--->Java講師mldn--->www.mldnjava.cn李興華--->Java講師mldn--->www.mldnjava.cn李興華--->Java講師mldn--->www.mldnjava.cn李興華--->Java講師mldn--->www.mldnjava.cn李興華--->Java講師mldn--->www.mldnjava.cn......從運(yùn)行結(jié)果可以發(fā)現(xiàn),生產(chǎn)者每生產(chǎn)一個(gè)資源就要等待消費(fèi)者取走,消費(fèi)者每取走一個(gè)就要等待生產(chǎn)者生產(chǎn),這樣就避免了重復(fù)生產(chǎn)和重復(fù)取走的問題。
四、線程的生命周期

其中3個(gè)方法:
suspend():暫時(shí)掛起線程
resume():回復(fù)掛起的線程
stop():停止線程
但這三種方法不推薦使用,因?yàn)槭褂眠@三種方法會(huì)產(chǎn)生死鎖的問題,在源代碼中這三個(gè)方法使用了@Deprecated
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注