要實現多線程能夠經過繼承Thread和實現Runnable接口。不過這二者之間存在一些區別。其中最重要的區別就是,若是一個類繼承Thread類,則不適合於多個線程共享資源,而實現了Runnable接口,就能夠方便地實現資源的共享。其實細提及來並非能不能資源共享的事情,是由於繼承Thread和實現Runnable接口這兩種方式新建的任務數自己就是不一樣的,線程與任務的對應機制也是不一樣的。html
範例1:繼承Thread類java
建立了多個thread,至關於建立了多個任務,每一個任務交由一個thread來完成。多線程
1 package test; 2 3 public class MyThreadDemo1 { 4 public static void main(String args[]) { 5 MyThread1 mt1 = new MyThread1(); 6 MyThread1 mt2 = new MyThread1(); 7 MyThread1 mt3 = new MyThread1(); 8 mt1.start(); 9 mt2.start(); 10 mt3.start(); 11 } 12 13 static class MyThread1 extends Thread { 14 private int ticket = 5; 15 16 @Override 17 public void run() { 18 // TODO Auto-generated method stub 19 for (int i = 0; i < 100; i++) 20 if (ticket > 0)// 當餘票大於0則買票 21 { 22 System.out.println("賣一張票:剩餘ticket=" + --ticket); // 這裏--ticket表示賣了一張票後的餘票 23 } 24 } 25 } 26 }
程序運行結果:ide
賣票:剩餘ticket=5 賣票:剩餘ticket=4 賣票:剩餘ticket=3 賣票:剩餘ticket=2 賣票:剩餘ticket=1 賣票:剩餘ticket=5 賣票:剩餘ticket=4 賣票:剩餘ticket=3 賣票:剩餘ticket=5 賣票:剩餘ticket=4 賣票:剩餘ticket=3 賣票:剩餘ticket=2 賣票:剩餘ticket=1 賣票:剩餘ticket=2 賣票:剩餘ticket=1
以上程序經過繼承Thread類實現多線程,程序中啓動了三個線程,可是三個線程分別買了各自的5張票,並無達到資源(Ticket)共享的目的。函數
範例2:實現Runable接口this
至關於只建立了一個任務,並把這個任務交給三個線程來完成。spa
1 package test; 2 3 public class MyRunableThreadDemo1 { 4 public static void main(String args[]) { 5 MyRunableThread1 mrt = new MyRunableThread1(); 6 Thread t1 = new Thread(mrt); 7 Thread t2 = new Thread(mrt); 8 Thread t3 = new Thread(mrt); 9 t1.start(); 10 t2.start(); 11 t3.start(); 12 } 13 14 static class MyRunableThread1 implements Runnable { 15 private int ticket = 5; 16 17 @Override 18 public void run() { 19 // TODO Auto-generated method stub 20 for (int i = 0; i < 100; i++) 21 if (ticket > 0)// 當餘票大於0則買票 22 { 23 System.out.println("賣一張票:剩餘ticket=" + --ticket); // 這裏--ticket表示賣了一張票後的餘票 24 } 25 } 26 } 27 }
程序運行結果:線程
賣票:剩餘ticket=5 賣票:剩餘ticket=4 賣票:剩餘ticket=3 賣票:剩餘ticket=2 賣票:剩餘ticket=1
從程序的運行結果中能夠清楚地發現,雖然啓動了3個線程, 可是三個線程一共才賣出去5張票,即ticket屬性是被全部線程所共享的。code
可見,實現Runnable接口相對於繼承Thrad類來講,有以下顯著優點:
1.適合多個相同程序代碼的線程去處理同一資源的狀況。
2.能夠避免因爲java單繼承特性帶來的侷限
3.加強了程序的健壯性,代碼可以被多個線程共享,代碼與數據時獨立的。htm
2. 多線程的同步
屢次運行範例2咱們發現獲得的結果可能都不相同。
範例2可能的輸出結果1
賣票:剩餘ticket=4 賣票:剩餘ticket=5 賣票:剩餘ticket=2 賣票:剩餘ticket=3 賣票:剩餘ticket=1
範例2可能的輸出結果2
賣票:剩餘ticket=4 賣票:剩餘ticket=2 賣票:剩餘ticket=1 賣票:剩餘ticket=5 賣票:剩餘ticket=1
範例2可能的輸出結果3
賣票:剩餘ticket=4 賣票:剩餘ticket=5 賣票:剩餘ticket=3 賣票:剩餘ticket=2 賣票:剩餘ticket=1 賣票:剩餘ticket=0 賣票:剩餘ticket=-1
出現票數爲負的狀況是由於:
線程1在執行 ticket--以前,線程2 進入了 if (ticket > 0) 這個判斷,這樣當線程1 ticket--以後ticket==0了,線程2再次執行ticket--那麼ticket==-1。至關於多執行了一次 ticket--
爲了解決範例2中出現的問題,咱們經過引入同步機制來解決問題。同步又分爲同步代碼塊和同步方法兩種類型。
1 package test; 2 3 public class MyRunableThreadDemo3 { 4 public static void main(String args[]) { 5 MyRunableThread1 mrt = new MyRunableThread1(); 6 Thread t1 = new Thread(mrt, "t1"); 7 Thread t2 = new Thread(mrt, "t2"); 8 Thread t3 = new Thread(mrt, "t3"); 9 t1.start(); 10 t2.start(); 11 t3.start(); 12 } 13 14 static class MyRunableThread1 implements Runnable { 15 private int ticket = 200; 16 17 @Override 18 public void run() { 19 20 //錯誤 21 // synchronized (this) { 22 // while (ticket > 0) { 23 // 24 //// try { 25 //// Thread.sleep(3); 26 //// } catch (InterruptedException e) { 27 //// e.printStackTrace(); 28 //// } 29 // System.out.println(Thread.currentThread().getName() 30 // + "賣了一張票票,剩餘ticket=" + --ticket);// 這裏--ticket表示賣了一張票後的餘票 31 // } 32 // } 33 /** 34 * 同步代碼塊 以前說到ticket出現負數的緣由是線程1在執行 ticket--以前,線程2 進入了 if (ticket > 0) 這個判斷, 35 * 這樣當線程1執行 ticket--以後ticket==0了,線程2再去執行ticket--,那麼ticket==-1。至關於多執行了一次ticket--, 36 * 所以咱們將synchronized(this)放在了if(ticket>0)以前。for(int i=0;i<100;i++)用來表示連續執行100次。這裏synchronized在 37 * for循環後面,所以每一個線程都執行100次,彼此都有可能鎖衝突。 38 * */ 39 for(int i=0;i<100;i++) 40 synchronized(this) 41 { 42 if(ticket>0) 43 { 44 try{ 45 Thread.sleep(30); 46 }catch(InterruptedException e) 47 { 48 e.printStackTrace(); 49 } 50 System.out.println(Thread.currentThread().getName() 51 + "賣了一張票票,剩餘ticket=" + --ticket);// 這裏--ticket表示賣了一張票後的餘票 52 } 53 } 54 } 55 } 56 }
以前說到ticket出現負數的緣由是「線程1在執行 --ticket以前,線程2 進入了 if (ticket > 0) 這個判斷, 這樣當線程1執行 ticket--以後ticket==0了,線程2再去執行ticket--,那麼ticket==-1,至關於多執行了一次ticket--」,
所以咱們將synchronized(this)放在了if(ticket>0)以前。咱們還能夠發現外部有一個執行通常次的for循環for(int i=0;i<100;i++),這用來表示run方法中的這synchronized代碼塊會被執行100次。須要注意的是隻有執行完synchronized代碼塊纔會釋放鎖。所以每個線程都有100次可能出現鎖衝突,一個線程須要等待另一個線程執行完synchronized代碼塊中的內容之後才能夠訪問這個代碼塊。
範例4:同步方法
1 package test; 2 3 public class MyRunableThreadDemo4 { 4 public static void main(String args[]) { 5 MyRunableThread1 mrt = new MyRunableThread1(); 6 Thread t1 = new Thread(mrt, "t1"); 7 Thread t2 = new Thread(mrt, "t2"); 8 Thread t3 = new Thread(mrt, "t3"); 9 t1.start(); 10 t2.start(); 11 t3.start(); 12 } 13 14 static class MyRunableThread1 implements Runnable { 15 private int ticket = 200; 16 17 @Override 18 public void run() { 19 for (int i = 0; i < 100; i++) { 20 sale(); 21 } 22 } 23 24 public synchronized void sale() { 25 if (ticket > 0) { 26 try { 27 Thread.sleep(30); 28 } catch (InterruptedException e) { 29 e.printStackTrace(); 30 } 31 System.out.println(Thread.currentThread().getName() 32 + "賣了一張票票,剩餘ticket=" + --ticket);// 這裏--ticket表示賣了一張票後的餘票 33 } 34 } 35 } 36 }
這裏將操做ticket資源的內容單獨抽取出來做爲一個方法來調用,而後同步該方法,保證同一時間只有一個進程調用該方法。
1 package test; 2 3 4 5 public class ThreadDeadLock { 6 public static void main(String args[]) { 7 Info info = new Info(); 8 // info做爲參數傳入兩個線程當中 9 ProducerThread pt = new ProducerThread(info); 10 ConsumerThread ct = new ConsumerThread(info); 11 12 Thread producer = new Thread(pt, "producer"); 13 Thread consumer = new Thread(ct, "consumer"); 14 producer.start(); 15 consumer.start(); 16 } 17 18 //資源類 19 static class Info { 20 private String name = "name"; 21 private String content = "content"; 22 23 public String getName() { 24 return name; 25 } 26 27 public void setName(String name) { 28 this.name = name; 29 } 30 31 public String getContent() { 32 return content; 33 } 34 35 public void setContent(String content) { 36 this.content = content; 37 } 38 } 39 40 // 生產者線程 41 static class ProducerThread implements Runnable { 42 private Info info = null; 43 44 // 構造函數,其參數是資源 45 public ProducerThread(Info info) { 46 this.info = info; 47 } 48 49 @Override 50 public void run() { 51 // boolean flag=false; 52 for (int i = 0; i < 10; i++) { 53 this.info.setName("name" + i); 54 try { 55 Thread.sleep(90); 56 } catch (InterruptedException e) { 57 // TODO Auto-generated catch block 58 e.printStackTrace(); 59 } 60 this.info.setContent("content" + i); 61 } 62 } 63 } 64 65 static class ConsumerThread implements Runnable { 66 private Info info = null; 67 68 // 構造函數,其參數是資源 69 public ConsumerThread(Info info) { 70 this.info = info; 71 } 72 73 @Override 74 public void run() { 75 for (int i = 0; i < 10; i++) { 76 try { 77 Thread.sleep(100); 78 } catch (InterruptedException e) { 79 // TODO Auto-generated catch block 80 e.printStackTrace(); 81 } 82 System.out.println(this.info.getName() + ":-->" 83 + this.info.getContent()); 84 } 85 } 86 } 87 88 }
程序輸出:
name1:-->content0 name2:-->content1 name3:-->content2 name4:-->content3 name5:-->content4 name6:-->content5 name7:-->content6 name8:-->content7 name9:-->content8 name9:-->content9
範例5存在兩個問題:
1.問題1:假設ProducerThread線程剛設置了name信息,尚未設置content信息,此時程序就切換到了ConsumerThread線程,那麼ConsumerThread線程獲取的是當前name與以前的content,因此輸出結果會出現(好比:name1:-->content0)。這是由於name與content沒有一塊兒設置的緣由,或者是說name與content信息不一樣步。
2.問題2:生產者放了若干次數據,消費者纔開始取數據,或者是,消費者取完一個數據後,尚未等到生產者放入新的數據,又重複取出已去過的數據。(好比出現name1:-->content0 name1:-->content0,這個能夠經過調節sleep來控制)
問題1 解決:加入同步
若是要爲操做加入同步,能夠經過定義同步方法的方式完成,即將設置名稱和內容定義在一個方法裏面,代碼如範例6所示。
範例6
1 package test; 2 3 public class ThreadDeadLock2 { 4 public static void main(String args[]) { 5 Info info = new Info(); 6 // info做爲參數傳入兩個線程當中 7 ProducerThread pt = new ProducerThread(info); 8 ConsumerThread ct = new ConsumerThread(info); 9 10 Thread producer = new Thread(pt, "producer"); 11 Thread consumer = new Thread(ct, "consumer"); 12 producer.start(); 13 consumer.start(); 14 } 15 16 // 資源類 17 static class Info { 18 private String name; 19 private String content; 20 21 //getter and setter 22 public String getName() { 23 return name; 24 } 25 public void setName(String name) { 26 this.name = name; 27 } 28 public String getContent() { 29 return content; 30 } 31 public void setContent(String content) { 32 this.content = content; 33 } 34 35 // 獲取name與content信息 36 public synchronized void get() { 37 38 try { 39 Thread.sleep(300); 40 } catch (InterruptedException e) { 41 // TODO Auto-generated catch block 42 e.printStackTrace(); 43 } 44 System.out.println(this.getName() + ":-->" + this.getContent()); 45 } 46 47 // 設置name與content信息 48 public synchronized void set(String name, String content) { 49 50 this.setName(name); 51 try { 52 Thread.sleep(300); 53 } catch (InterruptedException e) { 54 // TODO Auto-generated catch block 55 e.printStackTrace(); 56 } 57 this.setContent(content); 58 } 59 } 60 61 // 生產者線程 62 static class ProducerThread implements Runnable { 63 private Info info = null; 64 65 // 構造函數,其參數是資源 66 public ProducerThread(Info info) { 67 this.info = info; 68 } 69 70 @Override 71 public void run() { 72 73 for (int i = 0; i < 10; i++) { 74 this.info.set("name" + i, "content" + i); 75 } 76 } 77 } 78 79 static class ConsumerThread implements Runnable { 80 private Info info = null; 81 82 // 構造函數,其參數是資源 83 public ConsumerThread(Info info) { 84 this.info = info; 85 } 86 87 @Override 88 public void run() { 89 for (int i = 0; i < 10; i++) { 90 try { 91 Thread.sleep(100); 92 } catch (InterruptedException e) { 93 // TODO Auto-generated catch block 94 e.printStackTrace(); 95 } 96 this.info.get(); 97 } 98 } 99 } 100 }
程序運行結果:
name-0:-->content-0 name+1:-->content+1 name-2:-->content-2 name+3:-->content+3 name-4:-->content-4 name-6:-->content-6 name+7:-->content+7 name-8:-->content-8 name+9:-->content+9 name+9:-->content+9
從程序的運行結果中能夠發現,問題1:信息錯亂的問題已經解決,可是依然存在問題2:重複讀取的問題,以及漏讀信息的問題(好比上述輸出中name9:-->content9重複讀取,而name5:-->content5被漏讀了)。既然有重複讀取,則確定會有重複設置的問題,那麼對於這樣的問題,該如何解決呢?此時,就須要使用Object類。Object類是全部類的父類,在此類中wait、notify是對線程操做有所支持的。
若是想讓生產者不重複生產,消費者不重複消費,能夠設置有一個標誌位,假設標誌位爲boolean型變量,若是標誌位內容爲true,則表示能夠生產,可是不能取走,若是標誌位內容爲false,則表示能夠取走,不能生產。操做流程以下:
問題解決2——加入等待與喚醒
1 package edu.sjtu.erplab.thread; 2 3 class Info{ 4 private String name="name"; 5 private String content="content"; 6 private boolean flag=true; 7 public synchronized void set(String name,String content) 8 { 9 if(!flag)//標誌位爲false,不能夠生產 10 { 11 try { 12 super.wait(); 13 } catch (InterruptedException e) { 14 // TODO Auto-generated catch block 15 e.printStackTrace(); 16 } 17 } 18 this.setName(name); 19 try { 20 Thread.sleep(30); 21 } catch (InterruptedException e) { 22 // TODO Auto-generated catch block 23 e.printStackTrace(); 24 } 25 this.setContent(content); 26 flag=false;//修改標誌位爲false,表示生產者已經完成資源,消費者能夠消費。 27 super.notify();//喚醒消費者進程 28 } 29 30 public synchronized void get() 31 { 32 if(flag) 33 { 34 try { 35 super.wait(); 36 } catch (InterruptedException e) { 37 // TODO Auto-generated catch block 38 e.printStackTrace(); 39 } 40 } 41 try { 42 Thread.sleep(30); 43 } catch (InterruptedException e) { 44 // TODO Auto-generated catch block 45 e.printStackTrace(); 46 } 47 System.out.println(this.getName()+":-->"+this.getContent()); 48 flag=true;//修改標誌位爲true,表示消費者拿走資源,生產者能夠生產。 49 super.notify();//喚醒生產者進程。 50 } 51 52 53 public String getName() { 54 return name; 55 } 56 public void setName(String name) { 57 this.name = name; 58 } 59 public String getContent() { 60 return content; 61 } 62 public void setContent(String content) { 63 this.content = content; 64 } 65 66 } 67 68 class Producer implements Runnable{ 69 private Info info=null; 70 public Producer(Info info) 71 { 72 this.info=info; 73 } 74 75 76 @Override 77 public void run() { 78 boolean flag=false; 79 for(int i=0;i<10;i++) 80 if(flag) 81 { 82 this.info.set("name+"+i, "content+"+i); 83 flag=false; 84 } 85 else 86 { 87 this.info.set("name-"+i, "content-"+i); 88 flag=true; 89 } 90 } 91 } 92 93 class Consumer implements Runnable{ 94 private Info info=null; 95 public Consumer(Info info) 96 { 97 this.info=info; 98 } 99 @Override 100 public void run() { 101 for(int i=0;i<10;i++) 102 { 103 try { 104 Thread.sleep(10); 105 } catch (InterruptedException e) { 106 // TODO Auto-generated catch block 107 e.printStackTrace(); 108 } 109 this.info.get(); 110 } 111 112 } 113 } 114 115 public class ThreadDeadLock { 116 public static void main(String args[]) 117 { 118 Info info=new Info(); 119 Producer pro=new Producer(info); 120 Consumer con=new Consumer(info); 121 new Thread(pro).start(); 122 new Thread(con).start(); 123 } 124 125 }
程序運行結果:
name-0:-->content-0 name+1:-->content+1 name-2:-->content-2 name+3:-->content+3 name-4:-->content-4 name+5:-->content+5 name-6:-->content-6 name+7:-->content+7 name-8:-->content-8 name+9:-->content+9
參考:http://www.cnblogs.com/xwdreamer/archive/2011/11/20/2296931.html