Runnable和Thread基础---多线程学习笔记(二)
線程的創建方式有兩種:
implements Runnable和extends Thread。
繼承Thread類:
這里當調用start()方法的時候就會,線程就會進入線程隊列中,一旦線程獲取到了CPU的時間片,線程就會執行run()方法。當run()方法執行完畢,線程就會被銷毀。
實現Runnable接口:
package com.test.threadtest;import android.os.Bundle; import android.app.Activity; import android.view.Menu;public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mThread mthread = new mThread();Thread thread = new Thread(mthread);//創建線程thread.start();//啟動線程}class mThread implements Runnable{@Overridepublic void run() {}} }兩種方式的比較:
(1)Runnable方式可以避免Thread方式由于Java單繼承特性帶來的缺陷。
(2)Runnable的代碼可以被多個線程(Thread實例)共享,適合于多個線程處理同一資源的情況。
第二不同之處的解釋:
用實現Runnable接口的方式:
結果截圖:
用繼承Thread類的方式:
package com.test.threadtest;import android.os.Bundle; import android.app.Activity; import android.view.Menu;public class SecondActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//創建三個線程模擬三個窗口賣票mThread mthread1 = new mThread("窗口一");mthread1.start();mThread mthread2 = new mThread("窗口二");mthread2.start();mThread mthread3 = new mThread("窗口三");mthread3.start();}class mThread extends Thread{private int ticketsCont = 5;//一共有5張火車票private String name;//窗口,也就是線程的名字public mThread(String name) {this.name = name;}@Overridepublic void run() {while(ticketsCont>0){ticketsCont--;System.out.println(name+"賣了一張票,剩余票數為:"+ticketsCont);}}} }結果截圖:
造成這種結果差異的原因分析:
實現Runnable接口的代碼中,創建的三個線程中傳遞的是同一個Runnable對象,所引用的是同一個資源對象。
繼承Thread類的代碼中,創建的三個線程對象,每個線程對象中的資源也是獨立的,并不是共享的。
線程的生命周期:
從圖中可以看出線程的一生中一共有5種狀態,分別是:
(1)新建狀態: 也就是new出一個線程對象,新建一個線程對象,如:Thread thread = new Thread();
(2)就緒狀態: 創建了線程對象后,調用了線程的start()方法,此時線程只是進入了線程隊列,等待獲取CPU服務,具備了運行條件,但并不一定已經開始運行了。
因為這個時候CPU有可能正在執行其他的線程,而我們當前的這個線程可能要等待,當獲取到了CPU服務才會進入運行狀態。
(3)運行狀態: 處于就緒狀態的線程,一旦獲取了CPU資源,便進入到運行狀態,開始執行run()方法里面的邏輯。
(4)終止狀態: 線程的run()方法執行完畢,或者線程調用了stop()方法,線程便進入終止狀態。
(5)阻塞狀態: 一個正在run()方法中執行的線程在某些情況下,由于某種原因而暫時讓出了CPU資源,暫停了自己的執行,便進入了阻塞狀態,如調用了sleep()方法。
也就是說,如果在run()方法中調用sleep()方法,就會休眠自己進入阻塞狀態,也就會把自己所占用的CPU資源讓出來,讓其他線程去獲取CPU資源,當休眠時間過了之后,線程就會解除阻塞狀態進入就緒狀態,等待獲取CPU資源。
守護線程:
Java中線程有兩類:
(1)用戶線程:運行在前臺,執行具體的任務。
如:程序的主線程,連接網絡的子線程等都是用戶線程
(2)守護線程:運行在后臺,為其他前臺的用戶線程服務。
特點:
一旦所有用戶線程都結束運行,守護線程會隨JVM虛擬機一起結束工作。
應用:
數據庫連接池中的檢測線程,JVM虛擬機啟動后的監測線程
最常見的守護線程:垃圾回收線程
將線程設置為守護線程的方法:
可以通過調用Thread類的setDaemon(true)方法來設置當前的線程為守護線程。
使用守護線程的注意事項:
1.setDaemon(true)必須在start()方法之前調用,否則會拋出IllegalThreadStateException異常。
2.在守護線程中產生的新線程也是守護線程。
3.不是所有的任務都可以分配給守護線程來執行,比如讀寫操作或者計算邏輯。原因是:當我們在守護線程中執行著讀寫操作,當讀寫操作執行到一半的時候,所有的用戶線程都退出來了,那么守護線程就會隨JVM虛擬機一起結束工作,然而讀寫操作還沒進行完所以程序就崩潰了。
下面用一個例子解釋主線程與守護線程之間的關系:(守護線程在某一個時間內不停的往一個文件中寫數據,主線程則阻塞等待鍵盤的輸入,一旦主線程獲得了用戶的輸入阻塞就會解除,主線程繼續運行直到結束,而一旦主線程結束守護線程就會銷毀,但是守護線程中的寫入操作不一定就執行完畢了)
源碼:
package com.test.test;import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; import java.util.Scanner;public class Test {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();System.out.println("退出了主線程"+Thread.currentThread().getName());}}class DaemonThread implements Runnable{private String filePath = "C:/Users/Administrator/Desktop/daemon.txt";@Overridepublic void run() {System.out.println("進入了守護線程"+Thread.currentThread().getName());writeToFile();System.out.println("退出了守護線程"+Thread.currentThread().getName());}private void writeToFile() {OutputStream os = null;File filename = new File(filePath);try {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);}} catch (Exception e) {e.printStackTrace();}finally{try {os.close();} catch (Exception e) {e.printStackTrace();}}}}結果截圖:
生成的文件內容截圖:
通過這個例子:我們可以看到這個程序中只有一個用戶線程也就是主線程,當主線程結束的時候,守護線程就會自動銷毀(結果就是造成寫入的文件不完成)。
通過jstack生成線程快照
jstack的路徑位置:
位于jdk/bin目錄下
作用:
生成JVM當前時刻線程的快照(threaddump,即當前進程中所有線程 的信息)
目的:
幫助定位程序問題出現的原因,如:長時間停頓,CPU占用率過高等。
使用方法:
在命令提示符中輸入jstack,就會顯示出jstack的使用幫助。
如圖:
這里的-l可有可無,如果有就會打印出線程的一些鎖的信息
pid:就是任務管理其中進程的標號
如圖:當我啟動剛才的程序的時候進程中就會多出一個javaw.exe的進程
圖中可以看到PID一項,這里的PID就是我啟動的上邊的程序進程的標號。
使用jstack運行該程序的PID。如圖:
說明:
綠色框中的是進程中其中一個線程的信息。綠框下面的每一段都表示一個現成的信息。
該圖中線程狀態的說明圖:
該圖中阻塞狀態說明:
Blocked:如果一個線程正在等待一個監視器的鎖,那個該線程就處于Blocked狀態。eg: 被synchronized阻塞的線程
Waiting:如果一個線程正在無限期的等待另一個線程執行任務,那么這個線程就處于Waiting狀態。eg:當線程調用join()方法的時候就會有個線程進入Waiting狀態。
Timed_waiting:如果一個線程在指定的時間內等待另一個線程執行任務,那么這個線程就處于Timed_waiting狀態。eg:當程序調用了sleep()方法,并在sleep()方法中指定了休眠時間(eg:sleep(1000)形式),那么這個線程就進入了Timed_waiting狀態。
所以守護線程處于Timed_waiting狀態的原因是,我們在守護線程中調用了sleep(1000)方法。如圖:
繼續解釋命令提示符中的內容
繼續查看主線程信息:
總結
以上是生活随笔為你收集整理的Runnable和Thread基础---多线程学习笔记(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Activity的回调机制---Acti
- 下一篇: 文件编码和RandomAccessFil