Java多執行緒之一
程序與執行緒
程序
程序是程序實體的執行過程,是系統進行資源分配和排程的一個獨立單位,比如我們windows電腦上執行的一個程式就是一個程序。在傳統程序中程序是資源分配和排程的一個基本單位,在後來引入執行緒概念後,程序就變成了資源分配的基本單位但不是排程的基本單位。
為什麼要有執行緒
在說執行緒前,總結下程序的特點:
- 程序是一個可擁有資源的獨立單位;
- 程序是一個可獨立排程和分派的基本單位。
這樣來看的話好像是沒什麼問題,但是在多工環境中,不可能說讓所有任務排隊,前面的處理完了才處理後面的任務。如果要讓使用者==感覺==到任務都是一起執行的,那麼就必須在程序之間頻繁切換。問題在於如果要進行程序的切換需要做很多的工作,必須要儲存好當前CPU的上下文,好讓CPU下次被分配到當前程序時可以繼續往前執行,然後還需要設定新的程序的CPU上下文,在這個過程中會花費很多時間。由於這個原因就限制了系統中程序數目不能多。
為了解決這個限制,後來提出將程序的兩個屬性分開,由作業系統分開處理,即對於作為排程和分派的基本單位,但不同時作為擁有資源的單位;而對於擁有資源的基本單位,又不對其進行頻繁的切換。正是在這種思想的指導下,形成了執行緒的概念。
執行緒
在多執行緒作業系統中中,通常是在一個程序中包括多個執行緒,每個執行緒都是獨立排程和分派的基本單位。資源由程序來擁有,執行緒不擁有資源。同一個程序之間的執行緒切換不會導致程序的切換,只有不同程序間的執行緒切換才會導致程序切換。而且執行緒的切換則僅需儲存和設定少量暫存器內容,不會同程序切換需求建立和銷燬程序控制塊等,所以非常迅速,所以其十分適合高併發環境。
執行緒的狀態(Java)
public enum State { NEW,//新建 執行緒被建立,但是沒有呼叫start方法 RUNNABLE,//可執行 表示當前執行緒可以執行,但實際是否執行有cpu決定 BLOCKED,//阻塞 其他執行緒獲得鎖,當前執行緒被阻塞在獲得鎖處 WAITING,//等待 等待其他條件成熟進入可執行狀態 TIMED_WAITING,//計時等待 在一個指定時間內等待,超時後放棄 TERMINATED;//終止 執行緒執行完畢 }
執行緒的建立方式
Thread
繼承Thread類:
class TestThread extends Thread{ @Override public void run() { super.run(); //do working } }
Runnable
實現Runnable介面:
static class TestRunnale implements Runnable{ @Override public void run() { //do working } } public static void main(String[] args) { TestRunnale runnale = new TestRunnale(); Thread thread = new Thread(runnale); thread.start(); }
執行緒的中斷
不安全的中斷
在Thread
的api中提供了一些終止執行緒的方法,比如stop()
,suspend()
,resume()
,但是這些方法目前在JDK中已經被標記位過時,因為這些方法具有死鎖傾向,已經被明確表示不支援使用。
中斷執行緒API
interrupt()
中斷執行緒,本質是將執行緒的中斷標誌位設為true,其他執行緒向需要中斷的執行緒打個招呼。是否真正進行中斷由執行緒自己決定。
isInterrupted()
執行緒檢查自己的中斷標誌位
靜態方法Thread.interrupted()
將中斷標誌位復位為false
中斷標誌位
自定義一個Boolean型別的中斷標誌位,提供一箇中斷方法,執行緒一直迴圈檢測該標誌位,標誌位被設定為退出狀態是終止執行緒。
public class FlagCancel { static class Flag extends Thread{ //中斷標誌 public static boolean flag = false; @Override public void run() { int i = 0; while(!flag){ System.out.println(i++); if(i>=3){ try { Thread.sleep(200); //interrupt(); if(i == 10) cancel();//修改中斷狀態,退出執行緒 System.out.println("thread:" + isInterrupted()); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("cancel..."); } } } public static void cancel(){ flag = true; } } public static void main(String[] args) { Flag test = new Flag(); test.start(); test.setPriority(10);//這裡的設定優先順序其實沒什麼用。cpu不會理你的。。。 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("main:" + test.isInterrupted());//這裡屬於主執行緒(main) } }
正常來說上面的形式沒有什麼問題,我們寫程式碼的時候,提供一個修改中斷為狀態的方法,並根據我們自己的業務邏輯來定義什麼時候中斷,但是如果我們手動設定中斷就有問題了,將上面程式碼中註釋的interrupt();
開啟。interrupt()
方法是用來中斷執行緒的,但是在上面的邏輯中即使呼叫了該方法也不會立即中斷,而必須要等待中斷為被修改後才能退出。
安全的中斷
上面介紹了中斷相關的api和使用中斷標誌位來中斷執行緒,但是中斷標記位無法捕獲異常情況。但是isInterrupted()
方法會一直檢查執行緒的中斷狀態,所以我們可以用這個方法來實現安全的中斷。
public class SafeInterrupt extends Thread { private boolean flag = false; @Override public void run() { int i = 0; System.out.println(Thread.currentThread().getName() + ":" +Thread.currentThread().isInterrupted()); while (!flag && !Thread.currentThread().isInterrupted()) { System.out.println(i++); try { synchronized (this) { if (i > 3) { //Thread.sleep(1000 * 60 * 60 * 24); wait(); } } } catch (InterruptedException e) { e.printStackTrace(); } } } /** * 這裡必須將需要中斷的執行緒作為引數傳過來 * 用以進行中斷 * @param t(Thread) */ public void cancel(Thread t) { System.out.println("ready stop currentThread..."); flag = true; //將需要中斷的執行緒的中斷標誌位設定為true t.interrupt(); System.out.println(t.getName() + ":" + t.isInterrupted()); } public static void main(String[] args) throws InterruptedException { SafeInterrupt safeInterrupt = new SafeInterrupt(); safeInterrupt.start(); Thread.sleep(100); safeInterrupt.cancel(safeInterrupt); } }
不可中斷的情況
好了,到現在我們已經可以安全的處理執行緒的中斷了,但是還沒完,因為不是所有的執行緒都是會響應中斷的。比如IO的read()/write()
等就不會響應中斷。而如果我們想不讓其繼續阻塞的話就需要我們手動的關閉底層的套接字。
public class CloseSocket extends Thread { private Socket socket; private InputStream in; public CloseSocket(Socket socket, InputStream in) { this.socket = socket; this.in = in; } //重寫中斷方法 在中斷執行緒時中斷套接字 @Override public void interrupt() { try { //關閉底層套接字 socket.close(); } catch (IOException e) { e.printStackTrace(); }finally { //中斷執行緒 super.interrupt(); } } }
還有想死鎖之類的不響應中斷的情況用程式碼已經基本解決不了了,只能檢查程式碼修改重啟伺服器啦。