時間:2017年07月08日星期六
說明:本文部份內容均來自慕課網。@慕課網:http://www.imooc.com
教學源碼:無
學習源碼:https://github.com/zccodere/s...java
課程說明git
比較Thread和Runnable這兩種線程建立的方式,須要知道Thread和Runnable的基本建立方式。
課程目標和學習內容github
線程建立的兩種方式比較 線程的生命週期 線程的守護神:守護線程
方式一:繼承Thread類數據庫
方式二:實現Runnable接口安全
線程建立的兩種方式網絡
兩種方式的比較ide
Runnable方式能夠避免Thread方式因爲Java單繼承特性帶來的缺陷 Runnable的代碼能夠被多個線程(Thread實例)共享,適合於多個線程處理同一個資源的狀況
案例:模擬買票工具
代碼演示學習
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(); } }
運行結果
總共有5張票,可是三個窗口加在一塊兒賣了15張票。形成有些人買了票,上不了車,這種狀況不是咱們願意看到的。具體緣由,寫完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(); } }
運行結果
兩種方式的區別
線程的生命週期
建立
新建一個線程對象,如Threaf thd = new Thread()
就緒
建立了線程對象後,調用了線程的start()方法(注意:此時線程只是進入了線程隊列,等待獲取CPU服務,具有了運行的條件,但並不必定已經開始運行了)
運行
處於就緒狀態的線程,一旦獲取了CPU資源,便進入到運行狀態,開始執行run()方法裏面的邏輯
終止
線程的run()方法執行完畢,或者線程調用了stop()方法,線程便進入終止狀態
阻塞
一個正在執行的線程在某些狀況下,因爲某種緣由而暫時讓出了CPU資源,暫停了本身的執行,便進入了阻塞狀態,如調用了sleep()方法
阻塞狀態示意圖
Java線程有兩類
用戶線程:運行在前臺,執行具體的任務 程序的主線程、鏈接網絡的子線程等都是用戶線程 守護線程:運行在後臺,爲其餘前臺線程服務 特色:一旦全部用戶線程都結束運行,守護線程會隨JVM一塊兒結束工做 應用:數據庫鏈接池中的監測線程、JVM虛擬機啓動後的監測線程 最多見的守護線程:垃圾回收線程
如何設置守護線程
能夠經過調用Thread類的setDaemon(true)方法來設置當前的線程爲守護線程
注意事項
setDaemon(true)必須在start()方法以前調用,不然會拋出IllegalThreadStateException異常 在守護線程中產生的新線程也是守護線程 不是全部的任務均可以分配給守護線程來執行,好比讀寫操做或者計算邏輯
模擬場景示意圖
模擬場景說明
一共有兩個線程,一個主線程,一個守護線程。守護線程會在很長的時間內不停的往文件中寫數據,主線程會阻塞等待來自鍵盤的輸入。一旦主線程獲取到了用戶的輸入,這時候,阻塞就會解除掉,主線程繼續運行,直到結束。而一旦主線程結束,用戶線程就沒有了。這時候即便數據尚未寫完,守護線程也會隨虛擬機一塊兒結束運行。
代碼演示
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()); } }
經常使用查看線程工具
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佔有率很高的線程
課程總結
線程建立的兩種方式回顧 線程建立的兩種方式比較 線程的聲明週期 守護線程 jsrack生成線程快照
建議
多使用Runnable這種方式建立線程
補充
1.程序中的同一資源指的是同一個Runnable對象 2.安全的賣票程序中須要加入同步(Synchronized)