教學筆記:多線程之併發基礎(三)

線程是輕量級的進程,進程可以說是線程的容器。線程是程序執行的最小單位。使用多線程而不是使用多進程進行併發程序的設計,因為線程的切換和調度成本遠遠小於進程。

本文知識點目錄:

  • 線程的狀態
  • 線程的常見操作
  • --Daemon線程
  • --線程優先級
  • --wait與notify

線程的狀態


在Java程序中,線程有如下狀態

  • NEW(新建): 新的線程剛剛創建,還沒有啟動。
  • Runnable(就緒):線程已經調用了start方法,正在被JVM執行的時候,也有可能正在等待某個操作系統的資源。
  • Blocked(阻塞):有其它線程佔著鎖不釋放,當前線程阻塞與鎖
  • Waiting(等待):表示當前線程需要等待其它線程做出一些特定動作(通知或中斷),當調用以下方法時會進入此狀態。
  • Object.wait()沒有時間參數
  • Thread.join()沒有超時參數
  • LockSupport.park方法
  • Timed Waiting(等待並計時):指定時間waiting,一邊等,一邊計時,一般調用如下方法可以j進入此狀態。
  • Thread.sleep
  • Object.wait()帶有參數
  • Thread.join ()帶有超時參數
  • LockSupport.parkNanos
  • LockSupport.parkUntil
  • Terminated(終止) 線程完成了它的操作,也有可能錯誤的執行而終止。
教學筆記:多線程之併發基礎(三)

教學筆記:多線程之併發基礎(三)

線程操作

實現多線程

我們想要實現多線程常用的有兩種方法

  • 實現Runnable接口
  • 繼承自Thread線程

java只能單繼承,因此如果是採用繼承Thread的方法,那麼在以後進行代碼重構的時候可能會遇到問題,因為你無法繼承別的類了。

如果一個類繼承Thread,則不適合資源共享。但是如果實現了Runable接口的話,則很容易的實現資源共享。

注意這是在多線程情況下, 每個人運行的結果可能都不一樣。要想保住線程安全性,還需要額外的操作。

// 案例代碼,繼承線程變量無法共享public class ThreadOrRunnable extends Thread { private int count=5; private String name; public ThreadOrRunnable(String name) { this.name=name; } @Override public void run() {// super.run(); for (int i = 0; i < 5; i++) { count--; System.out.println(name + "運行 count=" + count); } } public static void main(String[] args) { ThreadOrRunnable threadOrRunnable = new ThreadOrRunnable("A"); ThreadOrRunnable threadOrRunnableB = new ThreadOrRunnable("B"); threadOrRunnable.start(); threadOrRunnableB.start();  }}//運行結果:A運行 count=4A運行 count=3A運行 count=2A運行 count=1B運行 count=4A運行 count=0B運行 count=3B運行 count=2B運行 count=1B運行 count=0// 案例 繼承自Runnable可以共享count變量public class RunnableTest implements Runnable{ private int count=15; @Override public void run() { for (int i = 0; i < 5; i++) { count--; System.out.println(Thread.currentThread().getName() + "運行: count=" + count); } } public static void main(String[] args) { RunnableTest runnableTest = new RunnableTest(); new Thread(runnableTest,"A").start(); new Thread(runnableTest,"B").start(); }}// 運行結果B運行: count=13A運行: count=13B運行: count=12A運行: count=11B運行: count=10A運行: count=9A運行: count=7A運行: count=6B運行: count=8B運行: count=5
Daemon線程

Daemon線程是一種支持型線程,在後臺調度及支持性工作。它有兩個點需要注意:

  • 必須在程序調用start方法之前調用Thread.setDaemon(true)方法才能設置為daemon線程,如果start()之後設置,會報IllegalThreadStateException異常
  • 在所有前臺線程執行完畢的時候,daemon線程會自動銷燬。
Thread thread = new Thread();thread.setDaemon(true);thread.start();
線程優先級

Java中的線程可以有自己的優先級,優先級高的線程在競爭資源時更可能搶佔資源,但是這只是一個概覽問題,並不是誰的優先級高,誰就一定先執行。

Thread thread = new Thread(); // MIN_PRIORITY = 1; // NORM_PRIORITY = 5; // MAX_PRIORITY = 10;thread.setPriority(int newPriority) thread.start();
wait與notify

wait和notify方法不是在Thread類中的,而是在Object類中,意味著任何對象都可以調用這兩個方法。

當一個線程A調用了obj.wait()方法,那麼線程A就會停止繼續執行,而是轉為waiting狀態。一直都其它線程調用obj.notify()方法為止。

教學筆記:多線程之併發基礎(三)

注意:

  • notify方法是從等待隊列中的線程隨機選擇一個,我們無法保證它喚醒的是那一個。notifyAll()方法會喚醒所有等待的線程。
  • object.wait()方法必須包含在synchronized語句中,wait或notify都需要首先獲得目標對象的監視器。

Object.wait和Thread.sleep方法都可以讓線程等待若干時間,它們喲徐誒區別。

  • wait可以被喚醒,sleep不可以
  • wait會釋放目標對象的鎖,而sleep不釋放任何鎖。
public class WaitNotify { static volatile boolean flag = true; static Object lock = new Object(); static class Wait implements Runnable{ @Override public void run() { synchronized (lock){ while (flag){ System.out.println(Thread.currentThread().getName() + " 時間: " + System.currentTimeMillis()); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //條件滿足的時候完成了工作 System.out.println("flag=" + flag + "任務完成"); } } } static class Notify implements Runnable{ @Override public void run() { synchronized (lock){ System.out.println(Thread.currentThread().getName() + " 持有鎖"); lock.notifyAll(); flag = false; try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized (lock){ System.out.println("再次持有鎖"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) throws InterruptedException { Thread waitThread = new Thread(new Wait(),"waitThread"); waitThread.start(); TimeUnit.SECONDS.sleep(1); Thread notifyThread = new Thread(new Notify(),"notifyThread"); notifyThread.start(); }}

//運行結果,調用notify之後,wait狀態會變為blocked狀態,然後再進入Runnable狀態。

教學筆記:多線程之併發基礎(三)

Wait與notify可以提煉出等待/通知的經典範式,氛圍兩部分:

等待方規則:

  1. 獲取對象的鎖
  2. 如果條件不滿足,那麼調用wait方法,被通知後仍要檢查條件
  3. 條件滿足則執行相應的邏輯

通知方規則:

  1. 獲得對象的鎖
  2. 改變條件
  3. 通知等待對象上的線程。

問題

關於Object.wait與notify,最後再留下來一個問題:

為什麼wait與notify方法要在Object類中調用而不是在Thread中?

最後

這次提到了一些Thread的基本概念,線程的狀態切換,線程的兩個屬性,最後提了一下wait與notify方法。下次講一下線程的常見用法。

//來源:簡書,著作權歸作者所有。


分享到:


相關文章: