Java 可中斷執行緒
PART.1 無法中斷的執行緒
一個無法中斷的執行緒的例子。
- public class UninterruptableThread
- {
- @SuppressWarnings("deprecation")
- public static void main(String[] args) throws Exception
- {
- Thread th = new Thread(new TestRunnable());
- // 啟動執行緒
- System.out.println("main: start thread.");
- th.start();
- // 等待2秒
- Thread.sleep(2000);
- // 中斷執行緒
- System.out.println("main: interrupt thread.");
- th.interrupt();
- // 等待2秒
- Thread.sleep(2000);
- // 停止執行緒
- System.out.println("main: stop thread.");
- th.stop();
- }
- private static class TestRunnable implements Runnable
- {
- @Override
- public void run()
- {
- System.out.println("Thread started.");
- while (true)
- {
- // 避免由於while(true)導致編譯失敗。
- if (false) break;
- }
- // 清理工作
- System.out.println("Thread ended.");
- }
- }
- }
- 輸出結果:
main: start thread.
Thread started.
main: interrupt thread.
main: stop thread.
- Thread物件的interrupt方法僅僅把該執行緒的一個標誌置為true,該方法本身並不包含任何中斷執行緒的操作。
- stop方法可以將執行緒中止,但通過輸出的結果可以發現,”Thread ended”並沒有被輸出,即執行緒本身不能進行任何清理工作。
PART.2 可中斷的執行緒
- 執行緒應不斷檢查isInterrupted是否為true,當其返回true時,應開始清理並結束執行緒。(Test1中的while迴圈)
- Thread.sleep方法會線上程被中斷時丟擲InterruptedException,執行緒可以捕獲該異常並開始清理和結束執行緒。(Test2中的Thread.sleep())
- 如果迴圈中不時呼叫Thread.sleep,可以處理isInterrupted。
可中斷執行緒的例子:
- public class GeneralTest
- {
- public static void main(String[] args) throws Exception
- {
- Thread th1 = new Thread(new Test1());
- Thread th2 = new Thread(new Test2());
- // 啟動 Test1 和 Test2 執行緒。
- System.out.println("main: starting 'Test1' and 'Test2'.");
- th1.start();
- th2.start();
- // 等待3秒。
- System.out.println("main: sleeping for 3 seconds.");
- Thread.sleep(3000);
- // 中斷 Test1 和 Test2 執行緒。
- System.out.println("main: interrupting 'Test1' and 'Test2'.");
- th1.interrupt();
- th2.interrupt();
- // 等待 Test1 和 Test2 執行緒結束。
- System.out.println("main: waiting for 'Test1' and 'Test2' to end.");
- th1.join();
- th2.join();
- System.out.println("main: end.");
- }
- private static class Test1 implements Runnable
- {
- @Override
- public void run()
- {
- System.out.println("Test1: start.");
- while (!Thread.currentThread().isInterrupted())
- {
- // 其他操作...
- System.out.print("");
- }
- System.out.println("Test1: end.");
- }
- }
- private static class Test2 implements Runnable
- {
- @Override
- public void run()
- {
- System.out.println("Test2: start.");
- try
- {
- while (true)
- {
- // 其他操作...
- Thread.sleep(1000);
- }
- }
- catch (InterruptedException e)
- {
- System.out.println("Test2: InterruptedException");
- }
- System.out.println("Test2: end.");
- }
- }
- }
PART.3 isInterrupted()和interrputed()方法的區別
- isInterrupted方法是例項方法,interrupted方法是靜態方法。
Thread.currentThread().isInterrupted()
Thread.interrupted()
- interrupted方法在呼叫之後會將中斷標誌置為false。在只對執行緒呼叫一次interrupt的前提下interrupted方法只會返回一次true。
- 使用interrupted方法判斷應確保在判斷之後開始結束執行緒。
isInterrupted和interrupted方法比較的例子:
- public class InterruptedStateTest
- {
- public static void main(String[] args) throws Exception
- {
- // "Test1"
- Thread th1 = new Thread(new Test1());
- // 啟動 Test1 執行緒,並在3秒之後中斷該執行緒。
- th1.start();
- Thread.yield();
- System.out.println("Test1 started... Waiting 3 seconds.");
- Thread.sleep(3000);
- System.out.println("Interrupting Test1...");
- th1.interrupt();
- Thread.sleep(1000);
- System.out.println("---------------------------------------");
- // “Test2"
- Thread th2 = new Thread(new Test2());
- // 啟動 Test2 執行緒,並在3秒之後中斷該執行緒。
- th2.start();
- Thread.yield();
- System.out.println("Test2 started... Waiting 3 seconds.");
- Thread.sleep(3000);
- System.out.println("Interrupting Test2...");
- th2.interrupt();
- Thread.yield();
- // 主執行緒結束。
- System.out.println("End of main.");
- }
- private static class Test1 implements Runnable
- {
- @Override
- public void run()
- {
- System.out.println("Test1 start...");
- while (true)
- {
- if (Thread.currentThread().isInterrupted())
- {
- if (Thread.currentThread().isInterrupted())
- {
- System.out.println("Interrupted...");
- break;
- }
- }
- }
- System.out.println("Test1 end...");
- }
- }
- private static class Test2 implements Runnable
- {
- @Override
- public void run()
- {
- // 記錄執行緒開始時間。
- long startTime = System.currentTimeMillis();
- System.out.println("Test2 start... " +
- "Automatically ends in 6 sec.");
- while (true)
- {
- // 連續判斷2次Thread.interrupted()
- if (Thread.interrupted())
- {
- if (Thread.interrupted())
- {
- System.out.println("Interrupted...");
- break;
- }
- }
- // 如果執行緒2執行超過6秒將自動結束。
- if (System.currentTimeMillis() - startTime > 6000 )
- {
- System.out.println("5 seconds...");
- break;
- }
- }
- System.out.println("Test2 end");
- }
- }
- }
例子中Test1連續判斷2次Thread.currentThread().isInterrupted(), Test1仍然可以正常中斷。
- if (Thread.currentThread().isInterrupted())
- {
- if (Thread.currentThread().isInterrupted())
- {
- // 結束執行緒。
- }
- }
Test2連續判斷2次Thread.interrupted(),因此Test2執行緒在被呼叫interrupt之後沒有結束。
- if (Thread.interrupted())
- {
- if (Thread.interrupted())
- {
- // 結束執行緒。
- }
- }
PART.4 處理阻塞
阻塞操作如BufferedReader的readLine方法,ServerSocket的accept方法將導致執行緒不能判斷isInterrupted(),因此執行緒中的阻塞不能永久阻塞。處理阻塞的方法有以下:
- 在呼叫阻塞方法時設定超時時間:
類 |
方法 |
超時後的處理 |
ReentrantLock ReadLock WriteLock |
tryLock(long, TimeUnit) |
返回false |
Condition |
await(long, TimeUnit) awaitNanos(long) awaitUntil(Date) |
返回false |
Future |
get(long, TimeUnit) |
TimeoutException |
CyclicBarrier |
await(long, TimeUnit) |
TimeoutException |
CountDownLatch |
await(long, TimeUnit) |
返回false |
Exchanger |
exchange(V, long, TimeUnit) |
TimeoutException |
Semaphore |
tryAcquire(long, TimeUnit) |
返回false |
BlockingQueue<E> |
offer(E, long, TimeUnit) poll(long, TimeUnit) |
返回false 返回null |
BlockingDeque<E> |
offerFirst(E, long, TimeUnit) offerLast(E, long, TimeUnit) poolFirst(long, TimeUnit) poolLast(long, TimeUnit) |
返回false
返回null |
ServerSocket |
accept() 通過setSoTimeout設定超時時間。 |
SocketTimeoutException |
- 該方法在阻塞時如果執行緒被中斷,可以丟擲一個異常:
類 |
方法 |
異常 |
Thread |
sleep(long) join() |
InterruptedException
|
ReentrantLock ReadLock WriteLock |
lockInterruptibly() |
InterruptedException |
Condition |
await() |
InterruptedException |
Future |
get() |
InterruptedException |
CyclicBarrier |
await() |
InterruptedException |
CountDownLatch |
await() |
InterruptedException |
Exchanger |
exchange(V) |
InterruptedException |
Semaphore |
acquire() |
InterruptedException |
BlockingQueue<E> |
put(E) take() |
InterruptedException |
BlockingDeque<E> |
putFirst(E) putLast(E) takeFirst(E) takeLast(E) |
InterruptedException |
- 呼叫不可中斷阻塞方法的可中斷版本:
類 |
阻塞方法 |
可中斷方法 |
ReentrantLock ReadLock WriteLock |
lock() |
tryLock() tryLock(long, TimeUnit) lockInterruptibly() |
Condition |
awaitUninterruptibly() |
await() await(long, TimeUnit) awaitNanos(long) awaitUntil(Date) |
Semaphore |
acquireUninterruptibly() |
acquire() tryAcquire() tryAcquire(long, TimeUnit) |
- 不能設定超時也不能丟擲異常的阻塞方法:
BufferedReader的readLine方法,ObjectInputStream得readObject方法。(如果底層流是通過Socket獲得,可以通過Socket設定超時)
PART.5 處理Thread.sleep()
1. 捕獲異常並結束執行緒
- 捕獲InterruptedException異常,開始清理操作並結束執行緒。
- public void run()
- {
- try
- {
- while (true)
- {
- // 需要進行的操作
- Thread.sleep(500);
- }
- }
- catch (InterruptedException e)
- {
- }
- // 清理操作
- }
2. 捕獲異常,再次呼叫interrupt
- public void run()
- {
- while (!Thread.currentThread().isInterrupted())
- {
- try
- {
- // 需要進行的操作 1
- Thread.sleep(500);
- }
- catch (InterruptedException e)
- {
- // 再次呼叫interrupt
- Thread.currentThread().interrupt();
- }
- try
- {
- // 需要進行的操作 2
- Thread.sleep(500);
- }
- catch (InterruptedException e)
- {
- // 再次呼叫interrupt
- Thread.currentThread().interrupt();
- }
- }
- // 清理操作
- }
PART.6 處理ReentrantLock和Condition
1. 通過lockInterruptibly方法中斷
- 捕獲lockInterruptibly方法可能跑出的InterruptedException,並結束執行緒。
- public void run()
- {
- try
- {
- while (true)
- {
- locker.lockInterruptibly();
- // 其他操作
- locker.unlock();
- }
- }
- catch (InterruptedException e)
- {
- }
- // 清理操作
- }
2. 通過不設定超時的tryLock方法中斷
- tryLock方法將不阻塞。
- 通過捕獲Thread.sleep的異常(或其他方法)中斷執行緒。
- public void run()
- {
- try
- {
- while (true)
- {
- if (locker.tryLock())
- {
- // 其他操作
- }
- Thread.sleep(500);
- }
- }
- catch (InterruptedException e)
- {
- }
- }
3. 通過設定超時的tryLock方法中斷
- 捕獲tryLock方法線上程中斷時丟擲的InterrupedException並結束執行緒。
- public void run()
- {
- try
- {
- while (true)
- {
- if (locker.tryLock(1, TimeUnit.SECONDS))
- {
- // 其他操作
- }
- }
- }
- catch (InterruptedException e)
- {
- }
- // 清理操作
- }
- 捕獲Condition的await方法丟擲的異常並結束執行緒。
- public void run()
- {
- try
- {
- while (true)
- {
- locker.lockInterruptibly();
- condition.await();
- // 其他操作
- locker.unlock();
- }
- }
- catch (InterruptedException e)
- {
- }
- // 清理操作
- }
5. 可中斷的Producer和Consumer示例
- produce方法線上程中斷時將跑出InterruptedException。
- run方法捕獲該異常,並中斷執行緒。
- public void run()
- {
- try
- {
- while (true)
- {
- produce();
- Thread.sleep((int)(Math.random() * 3000));
- }
- }
- catch (InterruptedException e)
- {
- }
- System.out.println("producer: end.");
- }
- private void produce() throws InterruptedException
- {
- locker.lockInterruptibly();
- try
- {
- // Produce
- int x = (int)(Math.random() * 100);
- queue.offer(x);
- emptyCondition.signalAll();
- System.out.printf("producer: %d and signal all. queue = %d /n",
- x, queue.size());
- }
- finally
- {
- locker.unlock();
- }
- }
- Consumer執行緒被中斷後不結束,直到佇列內所有資料被輸出。
- 不使用Thread.currentThread().isInterrupted()而定義isInt記錄中斷,可以避免中斷導致ReentrantLock方法的tryLock不能獲得鎖而直接丟擲異常。
- public void run()
- {
- // 執行緒是否被中斷
- boolean isInt = false;
- // 執行緒中斷後,將佇列內的資料全部讀出,再結束執行緒。
- while (!isInt || !queue.isEmpty())
- {
- try
- {
- consume();
- Thread.sleep((int)(Math.random() * 3000));
- }
- catch (InterruptedException e)
- {
- isInt = true;
- }
- }
- System.out.println("consumer: end.");
- }
- private void consume() throws InterruptedException
- {
- if (!locker.tryLock(5, TimeUnit.SECONDS))
- {
- // 沒有獲得鎖,不進行任何操作。 避免死鎖。
- return;
- }
- try
- {
- // Consume
- while (queue.isEmpty())
- {
- System.out.println("consumer: waiting for condition.");
- if (!emptyCondition.await(5, TimeUnit.SECONDS))
- {
- // 5秒沒有等待到條件,不進行任何操作。
- // 避免中斷後在此處等待。
- return;
- }
- }
- int x = queue.poll();
- System.out.printf("consumer: %d, queue=%d /n", x, queue.size());
- }
- finally
- {
- locker.unlock();
- }
- }
- 使用BlockingQueue處理Consumer和Producer問題。
- put和take方法可以線上程被中斷時丟擲InterruptedException。
Producer:
- public void run()
- {
- try
- {
- while (true)
- {
- int x = (int)(Math.random() * 100);
- queue.put(x);
- System.out.println("producer: " + x);
- Thread.sleep((int)(Math.random() * 3000));
- }
- }
- catch (InterruptedException e)
- {
- }
- System.out.println("producer: end.");
- }
Consumer:
- public void run()
- {
- try
- {
- while (true)
- {
- int x = queue.take();
- System.out.println("consumer: " + x);
- Thread.sleep((int)(Math.random() * 3000));
- }
- }
- catch (InterruptedException e)
- {
- }
- System.out.println("consumer: end.");
- }
PART.8 處理Socket和ServerSocket
1. 處理ServerSocket的accept方法
呼叫ServerSocket的setSoTimeout方法設定超時時間。捕獲並處理超時引起的SocketTimeoutException。不需要處理該異常。- public void run()
- {
- try
- {
- // 設定超時時間
- serverSocket.setSoTimeout(2000);
- }
- catch (SocketException e)
- {
- e.printStackTrace();
- System.exit(-1);
- }
- while (!Thread.currentThread().isInterrupted())
- {
- try
- {
- @SuppressWarnings("unused")
- Socket s = serverSocket.accept();
- }
- catch (SocketTimeoutException e)
- {
- // 超時,不進行任何處理,再次呼叫accept方法
- }
- catch (IOException e)
- {
- e.printStackTrace();
- }
- }
- // 清理工作
- }
2. 處理包裝自Socket的BufferedReader的readLine方法
- 呼叫Socket的setSoTimeout方法設定超時時間。
- BufferedReader的readLine方法在阻塞指定的時間後將丟擲SocketTimeoutException。
- 不需要處理該異常。
- public void run()
- {
- BufferedReader reader = null;
- try
- {
- // 建立測試用的連結
- serverSocket = new ServerSocket(10009);
- socket = new Socket("127.0.0.1", 10009);
- Thread.sleep(500);
- Socket s = serverSocket.accept();
- // 向socket傳送5行資料
- OutputStreamWriter w = new OutputStreamWriter(
- s.getOutputStream());
- w.write("line1 /n line2 /n line3 /n line4 /n line5 /n");
- w.flush();
- // 設定超時
- socket.setSoTimeout(1000);
- // 建立BufferedReader
- reader = new BufferedReader(
- new InputStreamReader(socket.getInputStream()));
- }
- catch (IOException e)
- {
- e.printStackTrace();
- System.exit(-1);
- }
- catch (InterruptedException e)
- {
- e.printStackTrace();
- System.exit(-1);
- }
- while (!Thread.currentThread().isInterrupted())
- {
- try
- {
- String s = reader.readLine();
- if (s == null)
- {
- // 流結束
- break;
- }
- // 輸出讀取的資料
- System.out.println("thread: " + s);
- }
- catch (SocketTimeoutException e)
- {
- System.out.println("thread: socket timeout.");
- }
- catch (IOException e)
- {
- e.printStackTrace();
- }
- }
- // 清理
- try { serverSocket.close(); } catch (Exception e) { }
- try { socket.close(); } catch (Exception e) { }
- System.out.println("thread: end.");
- }
3. 處理包裝自Socket的ObjectInputStream的readObject方法
- 與readLine的處理方法類似。
- 呼叫Socket的setSoTimeout方法設定超時時間。
- ObjectInputStream的readObject方法在阻塞指定的時間後將丟擲異常。
- 不需要處理該異常。
- public void run()
- {
- ObjectInputStream ois = null;
- try
- {
- // 建立測試用的連結
- serverSocket = new ServerSocket(10009);
- socket = new Socket("127.0.0.1", 10009);
- Thread.sleep(500);
- Socket s = serverSocket.accept();
- // 向socket傳送3個物件
- ObjectOutputStream oos = new ObjectOutputStream(
- s.getOutputStream());
- oos.writeObject(new TestData("object 1"));
- oos.writeObject(new TestData("object 2"));
- oos.writeObject(new TestData("object 3"));
- // 設定超時
- socket.setSoTimeout(1000);
- // 建立ObjectInputStream
- ois = new ObjectInputStream(socket.getInputStream());
- }
- catch (IOException e)
- {
- e.printStackTrace();
- System.exit(-1);
- }
- catch (InterruptedException e)
- {
- e.printStackTrace();
- System.exit(-1);
- }
- while (!Thread.currentThread().isInterrupted())
- {
- try
- {
- TestData s = (TestData)ois.readObject();
- if (s == null)
- {
- // 流結束
- break;
- }
- // 輸出讀取的資料
- System.out.println("thread: " + s.data);
- }
- catch (SocketTimeoutException e)
- {
- System.out.println("thread: socket timeout.");
- }
- catch (IOException e)
- {
- e.printStackTrace();
- }
- catch (ClassNotFoundException e)
- {
- e.printStackTrace();
- }
- }
- // 清理
- try { serverSocket.close(); } catch (Exception e) { }
- try { socket.close(); } catch (Exception e) { }
- System.out.println("thread: end.");
- }
其中,TestData是一個簡單的可序列化的類。
- private static class TestData implements Serializable
- {
- private static final long serialVersionUID = 6147773210845607198L;
- public String data;
- public TestData(String data)
- {
- this.data = data;
- }
- }
PART.9 總結
見“PART.2可中斷的執行緒”和“PART.4 處理阻塞”。
相關文章
- Java執行緒的中斷Java執行緒
- java中執行緒池的生命週期與執行緒中斷Java執行緒
- Java的Interrupt與執行緒中斷Java執行緒
- Java執行緒診斷Java執行緒
- 執行緒的中斷執行緒
- 執行緒中斷以及執行緒中斷引發的那些問題執行緒
- java多執行緒程式設計:你真的瞭解執行緒中斷嗎?Java執行緒程式設計
- Java併發程式設計之執行緒篇之執行緒中斷(三)Java程式設計執行緒
- 【Java面試】如何中斷一個正在執行的執行緒?Java面試執行緒
- 【雜談】執行緒中斷——Interrupt執行緒
- 跟著GPT學習Java執行緒中斷機制GPTJava執行緒
- Java 執行緒中斷(interrupt)與阻塞 (park)的區別Java執行緒
- Java中的執行緒Java執行緒
- Java中命名執行器服務執行緒和執行緒池Java執行緒
- JAVA中執行緒的建立Java執行緒
- Java 中的執行緒 threadJava執行緒thread
- JAVA中的執行緒世界Java執行緒
- Java中的多執行緒Java執行緒
- Java中的執行緒同步Java執行緒
- Java中的執行緒-1Java執行緒
- Java多執行緒中執行緒安全與鎖問題Java執行緒
- Java多執行緒-執行緒中止Java執行緒
- Java多執行緒-執行緒狀態Java執行緒
- Java多執行緒-執行緒通訊Java執行緒
- java多執行緒9:執行緒池Java執行緒
- java執行緒執行緒休眠,sleep方法Java執行緒
- java 多執行緒守護執行緒Java執行緒
- Java多執行緒(2)執行緒鎖Java執行緒
- 【java多執行緒】(二)執行緒停止Java執行緒
- Java多執行緒之執行緒中止Java執行緒
- Java中多執行緒的案例Java執行緒
- Java中如何保證執行緒順序執行Java執行緒
- java中如何給多執行緒中子執行緒傳遞引數?Java執行緒
- Android中的多執行緒斷點續傳Android執行緒斷點
- 【Java多執行緒】執行緒安全的集合Java執行緒
- Java多執行緒-執行緒池的使用Java執行緒
- Java執行緒池二:執行緒池原理Java執行緒
- Java執行緒池一:執行緒基礎Java執行緒
- Java執行緒篇——執行緒的開啟Java執行緒