JAVA多執行緒Thread VS Runnable詳解

Cheng發表於2015-03-12

要求

    • 必備知識

      本文要求基本瞭解JAVA程式設計知識。

    • 開發環境

      windows 7/EditPlus

    • 演示地址

      原始檔

 

程式與執行緒

程式是程式在處理機中的一次執行。一個程式既包括其所要執行的指令,也包括了執行指令所需的系統資源,不同程式所佔用的系統資源相對獨立。所以程式是重量級的任務,它們之間的通訊和轉換都需要作業系統付出較大的開銷。

執行緒是程式中的一個實體,是被系統獨立排程和分派的基本單位。執行緒自己基本上不擁有系統資源,但它可以與同屬一個程式的其他執行緒共享程式所擁有的全部資源。所以執行緒是輕量級的任務,它們之間的通訊和轉換隻需要較小的系統開銷。

Java支援多執行緒程式設計,因此用Java編寫的應用程式可以同時執行多個任務。Java的多執行緒機制使用起來非常方便,使用者只需關注程式細節的實現,而不用擔心後臺的多工系統。

Java語言裡,執行緒表現為執行緒類。Thread執行緒類封裝了所有需要的執行緒操作控制。在設計程式時,必須很清晰地區分開執行緒物件和執行執行緒,可以將執行緒物件看作是執行執行緒的控制皮膚。線上程物件裡有很多方法來控制一個執行緒是否執行,睡眠,掛起或停止。執行緒類是控制執行緒行為的唯一的手段。一旦一個Java程式啟動後,就已經有一個執行緒在執行。可通過呼叫Thread.currentThread方法來檢視當前執行的是哪一個執行緒。

執行緒建立的兩種方法

JAVA中建立執行緒可以通過繼承Thread類和實現Runnable介面來建立一個執行緒。Runnable方式可以避免Thread 方式由於JAVA單繼承特性帶來的缺陷。Runnable的程式碼可以被多個執行緒(Thread例項)共享,適合於多個執行緒處理同一資源的情況。

  • 繼承Thread類

class MyThread extends Thread{
    .....
    @Override
    public void run(){

    }

}

MyThread mt=new MyThread();
mt.start();
  • 實現Runnable介面

class MyThread implements Runnable{
    ....
    @Override
    public void run(){

    }
}

MyThread mt=new MyThread();
Thread td=new Thread(mt);
sd.start();

Thread&Runnable分別模擬賣火車票

  • Thread方式

class MyThread extends Thread
{
    private int ticketsCont=5; //一共有5張火車票

    private String name; //視窗, 也即是執行緒的名字

    public MyThread(String name){
        this.name=name;
    }

    @Override
    public void run(){
        
        while(ticketsCont>0){
            ticketsCont--; //如果還有票,就賣掉一張票
            System.out.println(name+"賣掉了1張票,剩餘票數為:"+ticketsCont);
        }
        
    }
}

public class TicketsThread
{
    public static void main(String args[]){
        
        //建立三個執行緒,模擬三個視窗賣票
        MyThread mt1=new MyThread("視窗1");
        MyThread mt2=new MyThread("視窗2");
        MyThread mt3=new MyThread("視窗3");

        //啟動三個執行緒,也即是視窗,開始賣票
        mt1.start();
        mt2.start();
        mt3.start();

    }
}
  • Runnable方式

class MyThread2 implements Runnable
{
    private int ticketsCont=1000; //一共有5張火車票
    
    @Override
    public void run(){
        
            
            while(true){
                 synchronized(this){
                    if(ticketsCont<=0){
                        break;
                    }
                    ticketsCont--; //如果還有票,就賣掉一張票
                    
                    System.out.println(Thread.currentThread().getName()+"賣掉了1張票,剩餘票數為:"+ticketsCont);
                    
                    /*try{
                        Thread.sleep(50);  //通過阻塞程式來檢視效果
                    }
                    catch(Exception e){
                        System.out.println(e);
                    }*/

                }
            }
        
    }



    /*@Override  //不加同步鎖
    public void run(){
        while(ticketsCont>0){
            ticketsCont--; //如果還有票,就賣掉一張票
            System.out.println(Thread.currentThread().getName()+"賣掉了1張票,剩餘票數為:"+ticketsCont);
        }
    }*/
}

public class TicketsRunnable
{
    public static void main(String args[]){
        
        MyThread2 mt=new MyThread2();
        //建立三個執行緒來模擬三個售票視窗
        Thread th1=new Thread(mt,"視窗1");
        Thread th2=new Thread(mt,"視窗2");
        Thread th3=new Thread(mt,"視窗3");

        //啟動三個執行緒,也即是三個視窗,開始賣票
        th1.start();
        th2.start();
        th3.start();
        

    }
}

執行緒的生命週期

2015-03-12_141728

  • 建立:新建一個執行緒物件,如Thread thd=new Thread()
  • 就緒:建立了執行緒物件後,呼叫了執行緒的start()方法(此時執行緒知識進入了執行緒佇列,等待獲取CPU服務 ,具備了執行的條件,但並不一定已經開始執行了)
  • 執行:處於就緒狀態的執行緒,一旦獲取了CPU資源,便進入到執行狀態,開始執行run()方法裡面的邏輯
  • 終止:執行緒的run()方法執行完畢,或者執行緒呼叫了stop()方法,執行緒便進入終止狀態
  • 阻塞:一個正在執行的執行緒在某些情況系,由於某種原因而暫時讓出了CPU資源,暫停了自己的執行,便進入了阻塞狀態,如呼叫了sleep()方法

執行緒的分類

  • 使用者執行緒:執行在前臺,執行具有的任務程式的主執行緒,連線網路的子執行緒等都是使用者執行緒
  • 守護執行緒:執行在後頭,為其他前臺執行緒服務。一旦所有使用者執行緒都結束執行,守護執行緒會隨JVM一起結束工作。可以通過呼叫Thread類的setDaemon(true)方法來設定當前的執行緒為守護執行緒,該方法必須在start()方法之前呼叫,否則會丟擲 IllegalThreadStateException異常。在守護執行緒中產生的新執行緒也是守護執行緒。不是所有的任務都可以分配給守護執行緒來執行,比如讀寫操作或者計算邏輯。

守護執行緒測試案例

2015-03-12_145001

場景:一個主執行緒,一個守護執行緒,守護執行緒會在很長一段時間內向本地檔案中寫入資料,主執行緒進入阻塞狀態等待使用者的輸入,一旦確認了使用者的輸入阻塞就會解除掉,主執行緒繼續執行直到結束,守護執行緒也會隨虛擬機器一同結束。

import java.io.*;
import java.util.Scanner;

class Daemon  implements Runnable
{
    @Override
    public void run(){
        System.out.println("進入守護執行緒");
        try{
            writeToFile();
        }
        catch(Exception e){
            e.printStackTrace();
        }

        System.out.println("退出守護執行緒");
    }


    private void writeToFile() throws Exception{
            File filename=new File("F:/慕課網(imooc)/細說多執行緒之Thread VS Runnable/daemon.txt");
            OutputStream os=new FileOutputStream(filename,true);
            int count=0;
            while(count<999){
                os.write(("\r\nword"+count).getBytes());
                System.out.println("守護執行緒"+Thread.currentThread().getName()+"向檔案中寫入word"+count++);
                Thread.sleep(1000);
            }
    }
}

public class DaemonThreadDemo
{
    public static void main(String args[]){
        System.out.println("進入主執行緒"+Thread.currentThread().getName());
        Daemon daemonThread=new Daemon();
        Thread thread =new Thread(daemonThread);
        thread.setDaemon(true);
        thread.start();

        Scanner sc=new Scanner(System.in);
        sc.next();

        System.out.println("退出主執行緒"+Thread.currentThread().getName());

        
    }
}

測試結果

2015-03-12_145755

使用jstack生成執行緒快照

jstack工具到jdk安裝目錄bin資料夾下。jstack能生成JVM當前時刻執行緒的快照(threaddump, 即當前程式中所有執行緒的資訊)。幫助定位程式問題出現的原因,如長時間停頓、CPU佔用率過高等。

使用方法

jstack [-l] <pid> : [-l]可有可無,表示關於鎖的二位資訊;<pid>表示程式ID。

2015-03-12_151225

總結

建議使用Runnable這種方式建立執行緒。 程式中的同一資源指的是同一個Runnable物件。安全的賣票程式中需要加入同步synchronized。

如以上文章或連結對你有幫助的話,別忘了在文章結尾處輕輕點選一下 “還不錯”按鈕或到頁面右下角點選 “贊一個” 按鈕哦。你也可以點選頁面右邊“分享”懸浮按鈕哦,讓更多的人閱讀這篇文章。

作者:Li-Cheng
由於本人水平有限,文章在表述和程式碼方面如有不妥之處,歡迎批評指正。留下你的腳印,歡迎評論哦。你也可以關注我,一起學習哦!

相關文章