Java多線程之Thread Vs Runnable

Thread和Runnable兩種方式的比較

  • Runnable方式能夠避免Thread方式因爲Java單繼承特性帶來的缺陷。
  • Runnable的代碼能夠被多個線程(Thread實例)共享,適合與多個線程處理同一資源的狀況。

首先咱們來看下代碼:java

這是實現Runnable接口的方式數據庫

class MyRunnable implements Runnable {

    private int ticketsCont = 5;

    @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) {

        MyRunnable myRunnable = new MyRunnable();
        Thread thread1 = new Thread(myRunnable, "窗口1");
        Thread thread2 = new Thread(myRunnable, "窗口2");
        Thread thread3 = new Thread(myRunnable, "窗口3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

//運行結果:
//窗口1賣了1張票,剩餘的票數爲:4
//窗口1賣了1張票,剩餘的票數爲:3
//窗口1賣了1張票,剩餘的票數爲:2
//窗口1賣了1張票,剩餘的票數爲:1
//窗口1賣了1張票,剩餘的票數爲:0

這是繼承Thread類的方式bash

class MyThread extends Thread {
    //火車票的總數
    private int ticketsCont = 5;
    //線程的名字
    private String name;


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

    @Override
    public void run() {
        while (ticketsCont > 0) {
            ticketsCont--;
            System.out.println(name + "賣了一張票,剩餘的票數爲:" + ticketsCont);
        }
    }
}

public class TicketsThread {
    public static void main(String[] args) {
        MyThread t1 = new MyThread("窗口1");
        MyThread t2 = new MyThread("窗口2");
        MyThread t3 = new MyThread("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
//運行結果:
//窗口1賣了一張票,剩餘的票數爲:4
//窗口1賣了一張票,剩餘的票數爲:3
//窗口1賣了一張票,剩餘的票數爲:2
//窗口1賣了一張票,剩餘的票數爲:1
//窗口1賣了一張票,剩餘的票數爲:0
//窗口3賣了一張票,剩餘的票數爲:4
//窗口3賣了一張票,剩餘的票數爲:3
//窗口3賣了一張票,剩餘的票數爲:2
//窗口3賣了一張票,剩餘的票數爲:1
//窗口3賣了一張票,剩餘的票數爲:0
//窗口2賣了一張票,剩餘的票數爲:4
//窗口2賣了一張票,剩餘的票數爲:3
//窗口2賣了一張票,剩餘的票數爲:2
//窗口2賣了一張票,剩餘的票數爲:1
//窗口2賣了一張票,剩餘的票數爲:0

運行得出結果,你就會發現兩種方式的不一樣:服務器

  • 繼承Thread類的方式會啓動三個線程,每一個線程都會賣5張票。
  • 實現Runnable接口的會啓動三個線程,三個線程共享一個資源。也就是三個線程賣5張票。

出現這種狀況的緣由是兩種不一樣的線程實現方式自己就決定了其是否能進行資源共享:網絡

Thread:app

一個線程只能啓動一次,經過Thread實現線程時,線程和線程所要執行的任務是捆綁在一塊兒的。
也就使得一個任務只能啓動一個線程,不一樣的線程執行的任務是不相同的,因此兩個線程之間是不能共享資源的,也不必。
固然若是必定要Thread的實現資源共享,那麼能夠在共享變量加上static關鍵字。

Runnable:jvm

一個任務能夠啓動多個線程,經過Runnable方式實現的線程,實際是開闢一個線程,將任務傳
遞進去,由此線程執行。能夠實例化多個 Thread對象,將同一任務傳遞進去,也就是一個任務能夠
啓動多個線程來執行它。這些線程執行的是同一個任務,因此他們的資源是共享。

因此說Runnable適合多個線程處理同一個資源的狀況。socket

線程的生命週期

image

  • 建立:新建一個新的線程對象,如Thread thread=new Thread();
  • 就緒:建立了線程對象後,調用了線程的start()方法(注意:此時線程只是進入了線程隊列,等待獲取cpu

服務,具有了運行的條件,可是並不必定已經開始運行了)。ide

  • 運行處於就緒狀態的線程,一旦獲取了cpu資源,便進入到了運行狀態,開始執行run()方法裏面的邏輯。
  • 終止:線程的run()方法中的邏輯執行完畢,或者線程調用了stop()方法,線程便進入終止狀態。
  • 阻塞:一個正在執行的線程在某些狀況下,因爲某種緣由而暫時讓出了cpu資源,暫停了本身的執行,便進入了阻塞狀態,如調用了sleep()方法。

守護線程

Java線程有兩類this

  • 用戶線程:運行在前臺,執行具體的任務,如任務的主線程,鏈接網絡的子線程等都是用戶線程。
  • 守護線程: 運行在後臺,爲其餘的前臺線程服務

    • 特色:一旦全部的用戶線程都運行結束,守護線程會隨着一塊兒結束工做。
    • 應用:數據庫鏈接池中的監測線程,JVM虛擬機啓動後的監測線程
    • 最多見的守護線程:垃圾回收線程

直接上代碼:

class DaemonRunnable implements Runnable {

    private int count;

    @Override
    public void run() {
        System.out.println("進入守護線程" + Thread.currentThread().getName());
        try {
            writeToFile();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("退出守護線程" + Thread.currentThread().getName());
    }

    private void writeToFile() throws Exception {
        File filename=new File("E:"+File.separator+"Daemon.txt");
        OutputStream os = new FileOutputStream(String.valueOf(filename), true);
        int count = 0;
        while (count < 999) {
            os.write(("\r\n world" + count).getBytes());
            System.out.println("守護線程" + Thread.currentThread().getName() + "向文件中寫入了world" + count++);
            Thread.sleep(1000);
        }
    }
}

public class DaemonDemo {
    public static void main(String []args){
        System.out.println("進入主線程"+Thread.currentThread().getName());
        DaemonRunnable daemonRunnable=new DaemonRunnable();
        Thread thread= new Thread(daemonRunnable);
        thread.setDaemon(true);//設置爲守護線程
        thread.start();
        Scanner scanner=new Scanner(System.in);
        scanner.next();
        System.out.println("退出主線程"+Thread.currentThread().getName());
    }
}

這時候經過的在Console中隨便輸入字符讓主線程中止。

進入主線程main
進入守護線程Thread-0
守護線程Thread-0向文件中寫入了world0
守護線程Thread-0向文件中寫入了world1
守護線程Thread-0向文件中寫入了world2
守護線程Thread-0向文件中寫入了world3
11
退出主線程main

能夠看到用戶線程中止的時候,守護線程也中止了。

注意事項

  • setDaemon(true)必須在start()方法以前調用,不然會拋出IllegalThreadStateException異常
  • 在守護線程中產生的新線程也是守護線程
  • 不是全部的任務均可以分配給守護線程來執行。好比讀寫操做或者邏輯計算

使用jstack

  • 做用:生成jvm當前時刻線程的快照(threaddump,即當前進程中全部線程的信息)
  • 目的:幫助定位程序問題實現的緣由,如長時間停頓,cpu佔用率太高等。

jstack是一個.exe的命令行程序,在jdk安裝目錄的bin目錄下。

個人jdk是默認安裝在C盤的,因此路徑是:C:\Program Files\Java\jdk1.8.0_131\bin

在bin目錄下打開Terminal,輸入jstack。

C:\Program Files\Java\jdk1.8.0_131\bin>jstack
Usage:
    jstack [-l] <pid>
        (to connect to running process)
    jstack -F [-m] [-l] <pid>
        (to connect to a hung process)
    jstack [-m] [-l] <executable> <core>
        (to connect to a core file)
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
        (to connect to a remote debug server)

Options:
    -F  to force a thread dump. Use when jstack <pid> does not respond (process is hung)
    -m  to print both java and native frames (mixed mode)
    -l  long listing. Prints additional information about locks
    -h or -help to print this help message

能夠看到jstack是經過pid碼來獲取線程快照信息的。

咱們能夠打開任務管理器的 -> 詳細服務,就能夠看到當前正在運行的程序的pid碼。

而後咱們啓動主線程開始運行,在Terminal中輸入:jstack -l 4528

4528是我當前運行的程序的pid碼,若是沒有相應的pid碼,那麼會提示拒絕訪問。

C:\Program Files\Java\jdk1.8.0_131\bin>jstack -l 804
2017-06-24 11:01:25
// 使用的Java虛擬機版本爲Oracle的HotSpot 64位服務器版本
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.131-b11 mixed mode):

"Thread-0" #11 daemon prio=5 os_prio=0 tid=0x00000000188ca800 nid=0x2560 waiting on condition [0x000000001941f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at Thread.demo.DaemonRunnable.writeToFile(DaemonDemo.java:32)
        at Thread.demo.DaemonRunnable.run(DaemonDemo.java:18)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - None

"Service Thread" #10 daemon prio=9 os_prio=0 tid=0x00000000187fc800 nid=0x252c runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"C1 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x00000000187cf000 nid=0x2038 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x0000000018774800 nid=0x20c8 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x0000000018771800 nid=0x2298 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000000001876f800 nid=0x16e0 runnable [0x0000000018e1e000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
        at java.net.SocketInputStream.read(SocketInputStream.java:171)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
        at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
        at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
        - locked <0x00000000d6132b90> (a java.io.InputStreamReader)
        at java.io.InputStreamReader.read(InputStreamReader.java:184)
        at java.io.BufferedReader.fill(BufferedReader.java:161)
        at java.io.BufferedReader.readLine(BufferedReader.java:324)
        - locked <0x00000000d6132b90> (a java.io.InputStreamReader)
        at java.io.BufferedReader.readLine(BufferedReader.java:389)
        at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)

   Locked ownable synchronizers:
        - None

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x0000000017418000 nid=0x1404 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x00000000173ce800 nid=0x1b20 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000000288d800 nid=0xf64 in Object.wait() [0x000000001871f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d5f08ec8> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
        - locked <0x00000000d5f08ec8> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

   Locked ownable synchronizers:
        - None

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000002882000 nid=0x148c in Object.wait() [0x000000001861f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d5f06b68> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x00000000d5f06b68> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

   Locked ownable synchronizers:
        - None

"main" #1 prio=5 os_prio=0 tid=0x000000000085e800 nid=0x27d4 runnable [0x000000000215e000]
   java.lang.Thread.State: RUNNABLE
        at java.io.FileInputStream.readBytes(Native Method)
        at java.io.FileInputStream.read(FileInputStream.java:255)
        at java.io.BufferedInputStream.read1(BufferedInputStream.java:284)
        at java.io.BufferedInputStream.read(BufferedInputStream.java:345)
        - locked <0x00000000d5f5a498> (a java.io.BufferedInputStream)
        at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
        at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
        at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
        - locked <0x00000000d60ada70> (a java.io.InputStreamReader)
        at java.io.InputStreamReader.read(InputStreamReader.java:184)
        at java.io.Reader.read(Reader.java:100)
        at java.util.Scanner.readInput(Scanner.java:804)
        at java.util.Scanner.next(Scanner.java:1369)
        at Thread.demo.DaemonDemo.main(DaemonDemo.java:45)

   Locked ownable synchronizers:
        - None

"VM Thread" os_prio=2 tid=0x0000000017386800 nid=0x1f68 runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00000000027a7800 nid=0x2a24 runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00000000027a9000 nid=0x71c runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00000000027aa800 nid=0x16bc runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00000000027ac000 nid=0x1d24 runnable

"VM Periodic Task Thread" os_prio=2 tid=0x000000001882e000 nid=0x1e30 waiting on condition

JNI global references: 33

我就拿Thread-0這個線程的信息作解釋:

Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.131-b11 mixed mode)

表示使用的是Oracle HotSpot64版本的JVM,爲服務器版本,客戶端版本爲Client

信息解釋:

"Thread-0" #11 daemon prio=5 os_prio=0 tid=0x00000000188ca800 nid=0x2560 waiting on condition [0x000000001941f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at Thread.demo.DaemonRunnable.writeToFile(DaemonDemo.java:32)
        at Thread.demo.DaemonRunnable.run(DaemonDemo.java:18)
        at java.lang.Thread.run(Thread.java:748)
   Locked ownable synchronizers:
        - None
  • Thread-0表示這個線程的名字
  • daemon表示該線程爲守護線程
  • prio=5表示線程的優先級
  • tidnid也是線程的16進制信息,結合其餘指令能夠很方便的定位出cpu佔有率很高的線程
  • java.lang.Thread.State: TIMED_WAITING (sleeping)表示線程的狀態爲TIMED_WAITING,這是調用了sleep()方法產生的結果
  • 須要注意的是,若是沒有加上-l參數的話,Locked ownable synchronizers:- None這一段信息是不會顯示出來的,這段表示的是鎖的

額外信息,None表示爲沒有。

至於其餘的線程信息的話,有興趣的人能夠去查閱相關的資料,在這裏就不去作更多的介紹。

相關文章
相關標籤/搜索