慕課網_《細說多線程之Thread VS Runnable》學習總結

時間:2017年07月08日星期六
說明:本文部份內容均來自慕課網。@慕課網:http://www.imooc.com
教學源碼:無
學習源碼:https://github.com/zccodere/s...java

第一章:課前準備

1-1 前言

課程說明git

比較Thread和Runnable這兩種線程建立的方式,須要知道Thread和Runnable的基本建立方式。

課程目標和學習內容github

線程建立的兩種方式比較
線程的生命週期
線程的守護神:守護線程

第二章:Thread VS Runnable

2-1 回顧線程建立的兩種方式

方式一:繼承Thread類數據庫

clipboard.png

方式二:實現Runnable接口安全

clipboard.png

線程建立的兩種方式網絡

clipboard.png

2-2 應用Thread模擬賣票

兩種方式的比較ide

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

案例:模擬買票工具

clipboard.png

代碼演示學習

1.編寫MyThread類this

package com.myimooc.ticketsthread;

/**
 * 使用 Thread 建立線程
 * @author ZhangCheng on 2017-07-08
 *
 */
public class MyThread extends Thread {
    
    /** 一共有5張火車票 */
    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 + "賣了1張票,剩餘票數爲:"+ticketsCont);
        }
    }
}

2.編寫TicketsThread類

package com.myimooc.ticketsthread;

/**
 * 主類-啓動線程類
 * @author ZhangCheng on 2017-07-08
 *
 */
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();
    }
    
}

運行結果

clipboard.png

clipboard.png

總共有5張票,可是三個窗口加在一塊兒賣了15張票。形成有些人買了票,上不了車,這種狀況不是咱們願意看到的。具體緣由,寫完Runnable後,會講解。

2-3 應用Runnable模擬賣票

代碼演示

1.編寫MyThread類

package com.myimooc.ticketsrunnable;

/**
 * 使用 Runnable 建立線程
 * @author ZhangCheng on 2017-07-08
 *
 */
public class MyThread implements Runnable {
    
    /** 一共有5張火車票 */
    private int ticketsCont = 5;
    
    // 寫買票邏輯
    @Override
    public void run() {
        while(ticketsCont > 0 ){
            // 若是還有票,就賣掉一張
            ticketsCont--;
            System.out.println(Thread.currentThread().getName() + "賣了1張票,剩餘票數爲:"+ticketsCont);
        }
    }
}

2.編寫TicketsRunnable類

package com.myimooc.ticketsrunnable;

/**
 * 主類-啓動線程類
 * @author ZhangCheng on 2017-07-08
 *
 */
public class TicketsRunnable {
    
    public static void main(String[] args) {
        
        MyThread mt = new MyThread();
        
        // 建立三個線程,模擬三個窗口賣票
        Thread th1 = new Thread(mt,"窗口1");
        Thread th2 = new Thread(mt,"窗口2");
        Thread th3 = new Thread(mt,"窗口3");
        
        // 啓動這三個線程,即窗口開始賣票
        th1.start();
        th2.start();
        th3.start();
        
    }
    
}

運行結果

clipboard.png

2-4 應用揭祕

兩種方式的區別

clipboard.png

第三章:線程的生命週期和守護線程

3-1 線程的生命週期

線程的生命週期

clipboard.png

建立

新建一個線程對象,如Threaf thd = new Thread()

就緒

建立了線程對象後,調用了線程的start()方法(注意:此時線程只是進入了線程隊列,等待獲取CPU服務,具有了運行的條件,但並不必定已經開始運行了)

運行

處於就緒狀態的線程,一旦獲取了CPU資源,便進入到運行狀態,開始執行run()方法裏面的邏輯

終止

線程的run()方法執行完畢,或者線程調用了stop()方法,線程便進入終止狀態

阻塞

一個正在執行的線程在某些狀況下,因爲某種緣由而暫時讓出了CPU資源,暫停了本身的執行,便進入了阻塞狀態,如調用了sleep()方法

阻塞狀態示意圖

clipboard.png

clipboard.png

clipboard.png

clipboard.png

3-2 守護線程理論知識

Java線程有兩類

用戶線程:運行在前臺,執行具體的任務
    程序的主線程、鏈接網絡的子線程等都是用戶線程
守護線程:運行在後臺,爲其餘前臺線程服務
    特色:一旦全部用戶線程都結束運行,守護線程會隨JVM一塊兒結束工做
    應用:數據庫鏈接池中的監測線程、JVM虛擬機啓動後的監測線程
    最多見的守護線程:垃圾回收線程

如何設置守護線程

能夠經過調用Thread類的setDaemon(true)方法來設置當前的線程爲守護線程

注意事項

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

3-3 守護線程代碼示例

模擬場景示意圖

clipboard.png

模擬場景說明

一共有兩個線程,一個主線程,一個守護線程。守護線程會在很長的時間內不停的往文件中寫數據,主線程會阻塞等待來自鍵盤的輸入。一旦主線程獲取到了用戶的輸入,這時候,阻塞就會解除掉,主線程繼續運行,直到結束。而一旦主線程結束,用戶線程就沒有了。這時候即便數據尚未寫完,守護線程也會隨虛擬機一塊兒結束運行。

代碼演示

1.編寫DaemonThread類

package com.myimooc.daemonthread;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;

/**
 * 守護線程。使用 Runnable 建立線程
 * @author ZhangCheng on 2017-07-08
 *
 */
public class DaemonThread implements Runnable {

    @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("d:" + File.separator + "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);
            count++;
            // 線程休眠1秒
            Thread.sleep(1000);
        }
        os.close();
    }
}

2.編寫DaemonThreadDemo類

package com.myimooc.daemonthread;

import java.util.Scanner;

/**
 * 主線程
 * @author ZhangCheng on 2017-07-08
 *
 */
public class DaemonThreadDemo {

    public static void main(String[] args) {
        
        System.out.println("進入主線程" + Thread.currentThread().getName());
        
        DaemonThread daemonThread = new DaemonThread();
        Thread thread = new Thread(daemonThread);
        thread.setDaemon(true);
        thread.start();
        
        Scanner sc = new Scanner(System.in);
        sc.next();
        sc.close();
        
        System.out.println("退出主線程" + Thread.currentThread().getName());
        
    }

}

3-4 使用jstack生成線程快照

經常使用查看線程工具

clipboard.png

jstack

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

使用命令

jstack -l PID
生成線程快照

快照案例

2017-07-08 23:49:46
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.101-b13 mixed mode):

"Thread-0" #10 daemon prio=5 os_prio=0 tid=0x000000001d209800 nid=0x2e00 waiting on condition [0x000000001dd2f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at com.myimooc.daemonthread.DaemonThread.writeToFile(DaemonThread.java:39)
        at com.myimooc.daemonthread.DaemonThread.run(DaemonThread.java:19)
        at java.lang.Thread.run(Thread.java:745)

   Locked ownable synchronizers:
        - None

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

   Locked ownable synchronizers:
        - None

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

   Locked ownable synchronizers:
        - None

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

   Locked ownable synchronizers:
        - None

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

   Locked ownable synchronizers:
        - None

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

   Locked ownable synchronizers:
        - None

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

   Locked ownable synchronizers:
        - None

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001bdba800 nid=0x1f10 in Object.wait() [0x000000001d12f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076b108ee0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
        - locked <0x000000076b108ee0> (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=0x00000000027a2800 nid=0x2214 in Object.wait() [0x000000001d02f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076b106b50> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x000000076b106b50> (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=0x000000000099d800 nid=0xf2c runnable [0x000000000228e000]
   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 <0x000000076b159560> (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 <0x000000076b1b9ce8> (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 com.myimooc.daemonthread.DaemonThreadDemo.main(DaemonThreadDemo.java:22)

   Locked ownable synchronizers:
        - None

"VM Thread" os_prio=2 tid=0x000000001bd98000 nid=0x2998 runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00000000026c6800 nid=0x11b0 runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00000000026c8000 nid=0x26a4 runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00000000026c9800 nid=0x2d6c runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00000000026cb000 nid=0xab0 runnable

"VM Periodic Task Thread" os_prio=2 tid=0x000000001d1f3000 nid=0x2b08 waiting on condition

JNI global references: 6

總結

1.若是有daemon標記,則當前線程爲守護線程
2.經過查看線程狀態(java.lang.Thread.State: TIMED_WAITING (sleeping))
    能夠幫助咱們定位到致使程序出現死鎖,或者是阻塞問題的緣由
3.tid和nid字段能夠幫助咱們找到CPU佔有率很高的線程

第四章:課程總結

4-1 課程總結

課程總結

線程建立的兩種方式回顧
線程建立的兩種方式比較
線程的聲明週期
守護線程
jsrack生成線程快照

建議

多使用Runnable這種方式建立線程

補充

1.程序中的同一資源指的是同一個Runnable對象
2.安全的賣票程序中須要加入同步(Synchronized)
相關文章
相關標籤/搜索