多線程與多進程(3)

原文:http://blog.csdn.net/luoweifu/article/details/46673975
做者:luoweifu
轉載請標名出處編程


單線程

任何程序至少有一個線程,即便你沒有主動地建立線程,程序從一開始執行就有一個默認的線程,被稱爲主線程,只有一個線程的程序稱爲單線程程序。以下面這一簡單的代碼,沒有顯示地建立一個線程,程序從main開始執行,main自己就是一個線程(主線程),單個線程從頭執行到尾。安全

【Demo1】:單線程程序服務器

1 public static void main(String args[]) {
2    System.out.println("輸出從1到100的數:");
3    for (int i = 0; i < 100; i ++) {
4       System.out.println(i + 1);
5    }
6 }

 


建立線程

單線程程序簡單明瞭,但有時沒法知足特定的需求。如一個文字處理的程序,我在打印文章的同時也要能對文字進行編輯,若是是單線程的程序則要等打印機打印完成以後你才能對文字進行編輯,但打印的過程通常比較漫長,這是咱們沒法容忍的。若是採用多線程,打印的時候能夠單獨開一個線程去打印,主線程能夠繼續進行文字編輯。在程序須要同時執行多個任務時,能夠採用多線程。markdown

在程序須要同時執行多個任務時,能夠採用多線程。Java給多線程編程提供了內置的支持,提供了兩種建立線程方法:1.經過實現Runable接口;2.經過繼承Thread類。多線程

Thread是JDK實現的對線程支持的類,Thread類自己實現了Runnable接口,因此Runnable是顯示建立線程必須實現的接口; Runnable只有一個run方法,因此無論經過哪一種方式建立線程,都必須實現run方法。咱們能夠看一個例子。ide

【Demo2】:線程的建立和使用post

 1 /**
 2  * Created with IntelliJ IDEA.
 3  * User: luoweifu
 4  * Date: 15-5-24
 5  * Time: 下午9:30
 6  * To change this template use File | Settings | File Templates.
 7  */
 8 
 9 /**
10  * 經過實現Runnable方法
11  */
12 class ThreadA implements Runnable {
13    private Thread thread;
14    private String threadName;
15    public ThreadA(String threadName) {
16       thread = new Thread(this, threadName);
17       this.threadName = threadName;
18    }
19 
20    //實現run方法
21    public void run() {
22       for (int i = 0; i < 100; i ++) {
23          System.out.println(threadName + ": " + i);
24       }
25    }
26 
27    public void start() {
28       thread.start();
29    }
30 }
31 
32 /**
33  * 繼承Thread的方法
34  */
35 class ThreadB extends Thread {
36    private String threadName;
37 
38    public ThreadB(String threadName) {
39       super(threadName);
40       this.threadName = threadName;
41    }
42 
43    //實現run方法
44    public void run() {
45       for (int i = 0; i < 100; i ++) {
46          System.out.println(threadName + ": " + i);
47       }
48    }
49 }
50 
51 public class MultiThread{
52 
53    public static void main(String args[]) {
54       ThreadA threadA = new ThreadA("ThreadA");
55       ThreadB threadB = new ThreadB("ThreadB");
56       threadA.start();
57       threadB.start();
58    }
59 }

 

說明:上面的例子中例舉了兩種實現線程的方式。大部分狀況下選擇實現Runnable接口的方式會優於繼承Thread的方式,由於:
1. 從 Thread 類繼承會強加類層次;
2. 有些類不能繼承Thread類,如要做爲線程運行的類已是某一個類的子類了,但Java只支持單繼承,因此不能再繼承Thread類了。測試


線程同步

線程與線程之間的關係,有幾種:this

模型一:簡單的線程,多個線程同時執行,但各個線程處理的任務絕不相干,沒有數據和資源的共享,不會出現爭搶資源的狀況。這種狀況下無論有多少個線程同時執行都是安全的,其執行模型以下:
spa

處理相互獨立的任務
圖 1:處理相互獨立的任務

 

模型二:複雜的線程,多個線程共享相同的數據或資源,就會出現多個線程爭搶一個資源的狀況。這時就容易形成數據的非預期(錯誤)處理,是線程不安全的,其模型以下:

多個線程共享相同的數據或資源
圖 2:多個線程共享相同的數據或資源

 

在出現模型二的狀況時就要考慮線程的同步,確保線程的安全。Java中對線程同步的支持,最多見的方式是添加synchronized同步鎖。

咱們經過一個例子來看一下線程同步的應用。

買火車票是你們春節回家最爲關注的事情,咱們就簡單模擬一下火車票的售票系統(爲使程序簡單,咱們就抽出最簡單的模型進行模擬):有500張從北京到贛州的火車票,在8個窗口同時出售,保證系統的穩定性和數據的原子性。

模擬火車票售票系統
圖 3:模擬火車票售票系統

 

【Demo3】:火車票售票系統模擬程序

 1 /**
 2  * 模擬服務器的類
 3  */
 4 class Service {
 5    private String ticketName;    //票名
 6    private int totalCount;        //總票數
 7    private int remaining;        //剩餘票數
 8 
 9    public Service(String ticketName, int totalCount) {
10       this.ticketName = ticketName;
11       this.totalCount = totalCount;
12       this.remaining = totalCount;
13    }
14 
15    public synchronized int saleTicket(int ticketNum) {
16       if (remaining > 0) {
17          remaining -= ticketNum;
18          try {        //暫停0.1秒,模擬真實系統中複雜計算所用的時間
19             Thread.sleep(100);
20          } catch (InterruptedException e) {
21             e.printStackTrace();
22          }
23 
24          if (remaining >= 0) {
25             return remaining;
26          } else {
27             remaining += ticketNum;
28             return -1;
29          }
30       }
31       return -1;
32    }
33 
34    public synchronized int getRemaining() {
35       return remaining;
36    }
37 
38    public String getTicketName() {
39       return this.ticketName;
40    }
41 
42 }
43 
44 /**
45  * 售票程序
46  */
47 class TicketSaler implements Runnable {
48    private String name;
49    private Service service;
50 
51    public TicketSaler(String windowName, Service service) {
52       this.name = windowName;
53       this.service = service;
54    }
55 
56    @Override
57    public void run() {
58       while (service.getRemaining() > 0) {
59          synchronized (this)
60          {
61             System.out.print(Thread.currentThread().getName() + "出售第" + service.getRemaining() + "張票,");
62             int remaining = service.saleTicket(1);
63             if (remaining >= 0) {
64                System.out.println("出票成功!剩餘" + remaining + "張票.");
65             } else {
66                System.out.println("出票失敗!該票已售完。");
67             }
68          }
69       }
70    }
71 }

測試程序:

 1 /**
 2  * 測試類
 3  */
 4 public class TicketingSystem {
 5    public static void main(String args[]) {
 6       Service service = new Service("北京-->贛州", 500);
 7       TicketSaler ticketSaler = new TicketSaler("售票程序", service);
 8       //建立8個線程,以模擬8個窗口
 9       Thread threads[] = new Thread[8];
10       for (int i = 0; i < threads.length; i++) {
11          threads[i] = new Thread(ticketSaler, "窗口" + (i + 1));
12          System.out.println("窗口" + (i + 1) + "開始出售 " + service.getTicketName() + " 的票...");
13          threads[i].start();
14       }
15 
16    }
17 }

結果以下:

窗口1開始出售 北京–>贛州 的票…
窗口2開始出售 北京–>贛州 的票…
窗口3開始出售 北京–>贛州 的票…
窗口4開始出售 北京–>贛州 的票…
窗口5開始出售 北京–>贛州 的票…
窗口6開始出售 北京–>贛州 的票…
窗口7開始出售 北京–>贛州 的票…
窗口8開始出售 北京–>贛州 的票…
窗口1出售第500張票,出票成功!剩餘499張票.
窗口1出售第499張票,出票成功!剩餘498張票.
窗口6出售第498張票,出票成功!剩餘497張票.
窗口6出售第497張票,出票成功!剩餘496張票.
窗口1出售第496張票,出票成功!剩餘495張票.
窗口1出售第495張票,出票成功!剩餘494張票.
窗口1出售第494張票,出票成功!剩餘493張票.
窗口2出售第493張票,出票成功!剩餘492張票.
窗口2出售第492張票,出票成功!剩餘491張票.
窗口2出售第491張票,出票成功!剩餘490張票.
窗口2出售第490張票,出票成功!剩餘489張票.
窗口2出售第489張票,出票成功!剩餘488張票.
窗口2出售第488張票,出票成功!剩餘487張票.
窗口6出售第487張票,出票成功!剩餘486張票.
窗口6出售第486張票,出票成功!剩餘485張票.
窗口3出售第485張票,出票成功!剩餘484張票.
……

在上面的例子中,涉及到數據的更改的Service類saleTicket方法和TicketSaler類run方法都用了synchronized同步鎖進行同步處理,以保證數據的準確性和原子性。

關於synchronized更詳細的用法請參見:《Java中Synchronized的用法


線程控制

在多線程程序中,除了最重要的線程同步外,還有其它的線程控制,如線程的中斷、合併、優先級等。

線程等待(wait、notify、notifyAll)

Wait:使當前的線程處於等待狀態;
Notify:喚醒其中一個等待線程;
notifyAll:喚醒全部等待線程。

詳細用法參見:《 Java多線程中wait, notify and notifyAll的使用


線程中斷(interrupt)

在Java提供的線程支持類Thread中,有三個用於線程中斷的方法:
public void interrupt(); 中斷線程。
public static boolean interrupted(); 是一個靜態方法,用於測試當前線程是否已經中斷,並將線程的中斷狀態 清除。因此若是線程已經中斷,調用兩次interrupted,第二次時會返回false,由於第一次返回true後會清除中斷狀態。
public boolean isInterrupted(); 測試線程是否已經中斷。

【Demo4】:線程中斷的應用

 1 /**
 2  * 打印線程
 3  */
 4 class Printer implements Runnable {
 5    public void run() {
 6       while (!Thread.currentThread().isInterrupted()) {     //若是當前線程未被中斷,則執行打印工做
 7          System.out.println(Thread.currentThread().getName() + "打印中… …");
 8       }
 9       if (Thread.currentThread().isInterrupted()) {
10          System.out.println("interrupted:" +  Thread.interrupted());       //返回當前線程的狀態,並清除狀態
11          System.out.println("isInterrupted:" +  Thread.currentThread().isInterrupted());
12       }
13    }
14 }

調用代碼:

 1 Printer printer = new Printer();
 2 Thread printerThread = new Thread(printer, "打印線程");
 3 printerThread.start();
 4 try {
 5    Thread.sleep(100);
 6 } catch (InterruptedException e) {
 7    e.printStackTrace();
 8 }
 9 System.out.println("有緊急任務出現,需中斷打印線程.");
10 System.out.println("中斷前的狀態:" + printerThread.isInterrupted());
11 printerThread.interrupt();       // 中斷打印線程
12 System.out.println("中斷前的狀態:" + printerThread.isInterrupted());
結果:

打印線程打印中… …
… …
打印線程打印中… …
有緊急任務出現,需中斷打印線程.
打印線程打印中… …
中斷前的狀態:false
打印線程打印中… …
中斷前的狀態:true
interrupted:true
isInterrupted:false

線程合併(join)

所謂合併,就是等待其它線程執行完,再執行當前線程,執行起來的效果就好像把其它線程合併到當前線程執行同樣。其執行關係以下:

線程合併的過程
圖 4:線程合併的過程

 

public final void join()
等待該線程終止

public final void join(long millis);
等待該線程終止的時間最長爲 millis 毫秒。超時爲 0 意味着要一直等下去。

public final void join(long millis, int nanos)
等待該線程終止的時間最長爲 millis 毫秒 + nanos 納秒

這個常見的一個應用就是安裝程序,不少大的軟件都會包含多個插件,若是選擇完整安裝,則要等全部的插件都安裝完成才能結束,且插件與插件之間還可能會有依賴關係。

【Demo5】:線程合併

 1 /**
 2  * 插件1
 3  */
 4 class Plugin1 implements Runnable {
 5 
 6    @Override
 7    public void run() {
 8       System.out.println("插件1開始安裝.");
 9       System.out.println("安裝中...");
10       try {
11          Thread.sleep(1000);
12       } catch (InterruptedException e) {
13          e.printStackTrace();
14       }
15       System.out.println("插件1完成安裝.");
16    }
17 }
18 
19 /**
20  * 插件2
21  */
22 class Plugin2 implements Runnable {
23 
24    @Override
25    public void run() {
26       System.out.println("插件2開始安裝.");
27       System.out.println("安裝中...");
28       try {
29          Thread.sleep(2000);
30       } catch (InterruptedException e) {
31          e.printStackTrace();
32       }
33       System.out.println("插件2完成安裝.");
34    }
35 }

合併線程的調用:

 1 System.out.println("主線程開啓...");
 2 Thread thread1 = new Thread(new Plugin1());
 3 Thread thread2 = new Thread(new Plugin2());
 4 try {
 5    thread1.start();   //開始插件1的安裝
 6    thread1.join();       //等插件1的安裝線程結束
 7    thread2.start();   //再開始插件2的安裝
 8    thread2.join();       //等插件2的安裝線程結束,才能回到主線程
 9 } catch (InterruptedException e) {
10    e.printStackTrace();
11 }
12 System.out.println("主線程結束,程序安裝完成!");

結果以下:

主線程開啓…
插件1開始安裝.
安裝中…
插件1完成安裝.
插件2開始安裝.
安裝中…
插件2完成安裝.
主線程結束,程序安裝完成!

優先級(Priority)

線程優先級是指得到CPU資源的優先程序。優先級高的容易得到CPU資源,優先級底的較難得到CPU資源,表現出來的狀況就是優先級越高執行的時間越多。

Java中經過getPriority和setPriority方法獲取和設置線程的優先級。Thread類提供了三個表示優先級的常量:MIN_PRIORITY優先級最低,爲1;NORM_PRIORITY是正常的優先級;爲5,MAX_PRIORITY優先級最高,爲10。咱們建立線程對象後,若是不顯示的設置優先級的話,默認爲5。

【Demo】:線程優先級

 1 /**
 2  * 優先級
 3  */
 4 class PriorityThread implements Runnable{
 5    @Override
 6    public void run() {
 7       for (int i = 0; i < 1000; i ++) {
 8          System.out.println(Thread.currentThread().getName() + ": " + i);
 9       }
10    }
11 }

調用代碼:

 1 //建立三個線程
 2 Thread thread1 = new Thread(new PriorityThread(), "Thread1");
 3 Thread thread2 = new Thread(new PriorityThread(), "Thread2");
 4 Thread thread3 = new Thread(new PriorityThread(), "Thread3");
 5 //設置優先級
 6 thread1.setPriority(Thread.MAX_PRIORITY);
 7 thread2.setPriority(8);
 8 //開始執行線程
 9 thread3.start();
10 thread2.start();
11 thread1.start()

從結果中咱們能夠看到線程thread1明顯比線程thread3執行的快。



若是您有什麼疑惑和想法,請在評論處給予反饋,您的反饋就是最好的測評師!因爲本人技術和能力有限,若是本博文有錯誤或不足之處,敬請諒解並給出您寶貴的建議!



原文:http://blog.csdn.net/luoweifu/article/details/46673975
做者:luoweifu 轉載請標名出處

相關文章
相關標籤/搜索