一、使用同步解決買票問題
解決資源共享的同步操作問題,可以使用同步代碼塊和同步方法兩種方式。
第一種:使用同步代碼塊必須指定一個需要同步的對象,通常將當前對象(this)設置成同步對象。
class SaleTask implements Runnable{ PRivate int ticket = 5;//剩余票數 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()+ "賣了一張票,剩余票數"+(--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個線程需要賣票 Thread t1 = new Thread(sale,"thread"+(i+1)); t1.start(); } }}運行結果:
線程thread1需要賣票線程thread3需要賣票線程thread2需要賣票線程thread6需要賣票線程thread7需要賣票線程thread4需要賣票線程thread8需要賣票線程thread5需要賣票線程thread1賣了一張票,剩余票數4張線程thread5賣了一張票,剩余票數3張線程thread8賣了一張票,剩余票數2張線程thread4賣了一張票,剩余票數1張線程thread7賣了一張票,剩余票數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()+ "賣了一張票,剩余票數"+(--ticket)+"張"); }else{ System.out.println("線程"+Thread.currentThread().getName()+ "需要賣票,已無票"); } }else{ System.out.println("線程"+Thread.currentThread().getName()+ "需要賣票,已無票"); } }運行結果:
線程thread1需要賣票線程thread1賣了一張票,剩余票數4張線程thread7需要賣票線程thread7賣了一張票,剩余票數3張線程thread3需要賣票線程thread3賣了一張票,剩余票數2張線程thread8需要賣票線程thread8賣了一張票,剩余票數1張線程thread4需要賣票線程thread4賣了一張票,剩余票數0張線程thread6需要賣票,已無票線程thread5需要賣票,已無票線程thread2需要賣票,已無票二、死鎖多個線程共享同一資源時需要進行同步,以保證資源操作的完整性,但是過多的同步就有可能產生死鎖。
class ThreadTask implements Runnable{ private static String resourceA = "Resource A"; private static String resourceB = "Resource B"; public boolean flag;//標記獲取資源的順序,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(); }}運行結果:
線程thread-1獲取了資源A線程thread-2獲取了資源B 兩個線程都在等待對方釋放手中的資源,這樣就產生了死鎖。三、線程操作案例——生產者與消費者
線程操作中一個典型的案例——生產者與消費者問題,生產者不斷生產,消費者不斷消費生產者生產的產品。
從中可以看出,生產者產生資源后將資源放到一個區域中,消費者從這個區域中取出產品,由于線程運行的不確定性,可能會產生下面兩種問題:
1)生產者剛向數據存儲空間中添加了信息的名稱,還沒有添加信息的內容,程序就切換到了消費者線程,這樣消費者線程讀取到的是更新后的信息的名稱和上一次信息的內容。
2)生產者連續更新了多次信息,消費者才開始讀取,或者,消費者連續多次讀取信息,生產者卻還沒來及更新。
1.程序的基本實現
無論是生產者還是消費者,操作的都是信息(資源),所以需要定義一個信息類。
//信息類(資源)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; }}生產者和消費者相當于兩個線程,操作同一個空間,分別實現Runnable接口
//生產者線程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++){ //循環50次讓生產者生產具體的內容 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; } } }}//消費者線程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(); }}運行結果:
李興華--->www.mldnjava.cnmldn--->Java講師李興華--->www.mldnjava.cnmldn--->Java講師mldn--->Java講師李興華--->www.mldnjava.cnmldn--->Java講師李興華--->www.mldnjava.cn李興華--->www.mldnjava.cnmldn--->Java講師......2.解決問題1——加入同步
將設置信息的方法和獲取信息的方法都設為同步方法,使得無論是設置信息還是讀取信息,都需要先獲取信息對象,在一段時間內,只有一個線程可以對資源進行操作。
//信息類(資源)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; }}生產者:
//生產者線程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; } } }}消費者:
//消費者線程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講師......從運行結果可以看出,信息錯亂的問題解決了,但是仍然存在信息重復讀取的問題,既然有重復讀取,就有重復設置,對于這樣的問題需要用到Object類。
3.Object類對線程的支持
1.Object類有以下幾種方法是對線程支持的:
從表中可知,可以將線程設為等待狀態,也可以喚醒線程。喚醒線程的方法有兩個,所有等待的線程一般會按照順序排列,如果用notify()方法喚醒,則會喚醒第一個線程,若用notifyAll()方法喚醒,則會喚醒所有的等待線程,哪一個線程的優先級高,哪一個線程就有可能先執行。
2.解決問題2——加入等待與喚醒
如果想讓生產者不重復生產,消費者不重復讀取,可以增加一個標志位,標志位為boolean型,
若為true,表示生產者可以生產,但消費者不可以讀取,如果是消費者線程則需要等待;
若為false,表示消費者可以讀取,但生產者不可以生產,如果是生產者線程則需要等待。
要完成以上功能,直接修改Info類即可,在Info類中增加一個標志位,通過判斷標志位完成等待與喚醒操作。
//信息類(資源)class Info { privateString name = "李興華"; //資源name privateString content = "Java講師"; //資源content privateboolean flag = false; //true,表示可以生產,不能消費 //false表示可以消費,不能生產 //生產資源 publicsynchronized void set(String name,String content){ if(!flag){ //為false時,生產者線程等待 try{ super.wait(); //wait()方法會讓線程釋放手中的鎖 }catch (InterruptedException e) { e.printStackTrace(); } } setName(name); try{ Thread.sleep(90); //加入延遲 }catch(InterruptedExceptione){ e.printStackTrace(); } setContent(content); this.flag= false; super.notify(); //喚醒等待的線程 } //消費資源 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; }}運行結果:
李興華--->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......從運行結果可以發現,生產者每生產一個資源就要等待消費者取走,消費者每取走一個就要等待生產者生產,這樣就避免了重復生產和重復取走的問題。
四、線程的生命周期
其中3個方法:
suspend():暫時掛起線程
resume():回復掛起的線程
stop():停止線程
但這三種方法不推薦使用,因為使用這三種方法會產生死鎖的問題,在源代碼中這三個方法使用了@Deprecated
新聞熱點
疑難解答