java線程小結3

1. 多線程概述

  要實現多線程能夠經過繼承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
View Code

以上程序經過繼承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
View Code

從程序的運行結果中能夠清楚地發現,雖然啓動了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
View Code

範例2可能的輸出結果2

賣票:剩餘ticket=4
賣票:剩餘ticket=2
賣票:剩餘ticket=1
賣票:剩餘ticket=5
賣票:剩餘ticket=1
View Code

範例2可能的輸出結果3

賣票:剩餘ticket=4
賣票:剩餘ticket=5
賣票:剩餘ticket=3
賣票:剩餘ticket=2
賣票:剩餘ticket=1
賣票:剩餘ticket=0
賣票:剩餘ticket=-1
View Code

出現票數爲負的狀況是由於:

線程1在執行 ticket--以前,線程2 進入了 if (ticket > 0) 這個判斷,這樣當線程1 ticket--以後ticket==0了,線程2再次執行ticket--那麼ticket==-1。至關於多執行了一次 ticket--

3.兩種線程同步方法

爲了解決範例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 }
View Code

以前說到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 }
View Code

這裏將操做ticket資源的內容單獨抽取出來做爲一個方法來調用,而後同步該方法,保證同一時間只有一個進程調用該方法。

4.生產者消費者案例

 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 }
View Code

程序輸出:

name1:-->content0
name2:-->content1
name3:-->content2
name4:-->content3
name5:-->content4
name6:-->content5
name7:-->content6
name8:-->content7
name9:-->content8
name9:-->content9
View Code

範例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 }
View Code

程序運行結果:

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
View Code

從程序的運行結果中能夠發現,問題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 }
View Code

程序運行結果:

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
View Code

 

參考:http://www.cnblogs.com/xwdreamer/archive/2011/11/20/2296931.html

相關文章
相關標籤/搜索