程式與執行緒
程式是程式在處理機中的一次執行。一個程式既包括其所要執行的指令,也包括了執行指令所需的系統資源,不同程式所佔用的系統資源相對獨立。所以程式是重量級的任務,它們之間的通訊和轉換都需要作業系統付出較大的開銷。
執行緒是程式中的一個實體,是被系統獨立排程和分派的基本單位。執行緒自己基本上不擁有系統資源,但它可以與同屬一個程式的其他執行緒共享程式所擁有的全部資源。所以執行緒是輕量級的任務,它們之間的通訊和轉換隻需要較小的系統開銷。
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(); } }
執行緒的生命週期
- 建立:新建一個執行緒物件,如Thread thd=new Thread()
- 就緒:建立了執行緒物件後,呼叫了執行緒的start()方法(此時執行緒知識進入了執行緒佇列,等待獲取CPU服務 ,具備了執行的條件,但並不一定已經開始執行了)
- 執行:處於就緒狀態的執行緒,一旦獲取了CPU資源,便進入到執行狀態,開始執行run()方法裡面的邏輯
- 終止:執行緒的run()方法執行完畢,或者執行緒呼叫了stop()方法,執行緒便進入終止狀態
- 阻塞:一個正在執行的執行緒在某些情況系,由於某種原因而暫時讓出了CPU資源,暫停了自己的執行,便進入了阻塞狀態,如呼叫了sleep()方法
執行緒的分類
- 使用者執行緒:執行在前臺,執行具有的任務程式的主執行緒,連線網路的子執行緒等都是使用者執行緒
- 守護執行緒:執行在後頭,為其他前臺執行緒服務。一旦所有使用者執行緒都結束執行,守護執行緒會隨JVM一起結束工作。可以通過呼叫Thread類的setDaemon(true)方法來設定當前的執行緒為守護執行緒,該方法必須在start()方法之前呼叫,否則會丟擲 IllegalThreadStateException異常。在守護執行緒中產生的新執行緒也是守護執行緒。不是所有的任務都可以分配給守護執行緒來執行,比如讀寫操作或者計算邏輯。
守護執行緒測試案例
場景:一個主執行緒,一個守護執行緒,守護執行緒會在很長一段時間內向本地檔案中寫入資料,主執行緒進入阻塞狀態等待使用者的輸入,一旦確認了使用者的輸入阻塞就會解除掉,主執行緒繼續執行直到結束,守護執行緒也會隨虛擬機器一同結束。
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()); } }
測試結果
使用jstack生成執行緒快照
jstack工具到jdk安裝目錄bin資料夾下。jstack能生成JVM當前時刻執行緒的快照(threaddump, 即當前程式中所有執行緒的資訊)。幫助定位程式問題出現的原因,如長時間停頓、CPU佔用率過高等。
使用方法
jstack [-l] <pid> : [-l]可有可無,表示關於鎖的二位資訊;<pid>表示程式ID。
總結
建議使用Runnable這種方式建立執行緒。 程式中的同一資源指的是同一個Runnable物件。安全的賣票程式中需要加入同步synchronized。
如以上文章或連結對你有幫助的話,別忘了在文章結尾處輕輕點選一下 “還不錯”按鈕或到頁面右下角點選 “贊一個” 按鈕哦。你也可以點選頁面右邊“分享”懸浮按鈕哦,讓更多的人閱讀這篇文章。