并發系列1 Java并發編程基礎

參考:
《Java并發編程的藝術》第四章
《Java多線程編程核心技術》
博客 http://www.lxweimin.com/p/8a04b5ec786c Java多線程基礎
博客 http://www.lxweimin.com/p/12af2d966c13 Java并發編程1

一.線程簡介

1.線程和進程
  • 進程是系統進行資源分配和調度的一個獨立單位,現代操作系統運行程序時會創建進程
  • 線程也叫輕量級進程,是現代操作系統調度的最小單元,一個進程中可以有多個線程,每個線程都擁有各自的計數器、堆棧和局部變量等屬性,并能夠訪問共享的內存變量
2.為什么使用多線程
  • 更多的計算核心:充分利用多核處理器的硬件優勢,將計算邏輯分配給多個處理器同時執行
  • 更快的響應時間:在業務邏輯復雜的場景中,將數據一致性不強的操作派發給其他線程處理
  • 更好的編程邏輯:Java提供了良好并且一致的多線程編程模型,方便開發者完成多線程開發
3.上下文切換
  • CPU切換運行的線程時存儲和恢復CPU的過程,使得線程能夠從中斷點恢復執行
  • 線程上下文切換過程中會記錄程序計數器、CPU寄存器狀態等
  • 多線程環境中上下文切換會帶來一定的性能開銷
4.線程優先級
  • 現代操作系統采取分時的形式調度執行的線程
  • 線程在獲取操作系統分出的時間片后開始執行,時間片用完后停止執行,等待再次獲得時間片
  • 線程優先級決定線程獲取CPU時間片的優先級
  • 注意:Java線程優先級在某些操作系統中無效
5.線程的狀態
狀態名稱 說明
NEW 初始狀態,線程被構建,但還沒有調用start()方法
RUNNABLE 運行狀態,包括線程在操作系統中就緒和運行兩種情況
BLOCKED 阻塞狀態,表示線程阻塞于鎖
WAITING 等待狀態,進入等待狀態的線程需要等待其他線程的特定動作(通知或中斷)
TIME_WAITING 超時等待狀態,進入超時等待狀態的線程可以在指定的時間自行返回
TERMINATED 終止狀態,表示當前線程已經執行完畢
線程狀態切換
6. Daemon線程
  • 支持型線程,用作程序中后臺調度以及支持型工作
  • JVM不存在非Daemon線程時,Daemon線程將自動結束,JVM退出
  • 注意,在JVM退出時,Daemon線程中的finally塊可能不會執行

二.線程啟動和終止

1.構造線程
  • void init(ThreadGroup g, Runnable target, String name,long stackSize,AccessControlContext acc)方法完成線程構造
  • 新構造的線程對象由其parent線程進行空間分配,繼承了parent線程的Daemon、優先級和加載資源的contextClassLoader以及可繼承的ThreadLocal,同時獲得唯一的線程ID
2.線程的實現
  • 繼承Thread類,重寫run()方法,Thread類本身實現了Runnable接口
  • 實現Runnable接口,重寫run()方法
  • 使用ExecutorService、Callable、Future實現有返回結果的多線程
3.線程中斷
  • 一個線程應該自行停止,而非由其他線程強制中斷或停止
  • Thread.stop()不保證資源的正確釋放、Thread.suspend()暫停時不釋放鎖容易導致死鎖、Thread.resume()等三個方法都已廢棄
  • 每個線程均有一個中斷標志位,表示是否有其他線程對該線程進行了中斷操作
  • 當對一個線程調用interrupt()方法時
    1)如果線程處于等待狀態(如sleep、wait、join)時,線程將立即退出等待狀態,并拋出一個interruptedException異常,僅此而已
    2)如果線程處于正常活動狀態,會將該線程的中斷標志設置為true,僅此而已。被設置中斷標志的線程將繼續正常運行,不受影響
  • 所以interrupt()并不能真正的中斷線程,需要被調用的線程進行配合。如果一個線程有被中斷的需求,可以這樣做
    1)在正常運行任務時,使用isInterrupted()方法經常檢查本線程的中斷標志位,如果被設置了中斷標志就自行停止
    2)線程處于等待狀態時,catch到InterruptedException異常后退出線程
  • Thread.interrupted()方法將清除中斷標志位,但并不代表線程又恢復,僅代表線程已響應該中斷信號然后重置為可再次接收信號的狀態
  • 處于等待的線程在調用interrupt()方法后拋出InterruptedException前,JVM將先清除線程的中斷標志位
Modifier and Type Method Description
void interrupt() Interrupts this thread
static boolean interrupted() Tests whether the current thread has been interrupted
boolean isInterrupted() Tests whether this thread has been interrupted

三.線程間通信

1.volatile和synchronized
  • Java支持多個線程同時訪問共享對象,現代多核處理器為了加速程序運行,每個線程會擁有共享對象的一份拷貝,由此引出內存可見性問題——一個線程看到的變量并一定是最新的
  • volatile:修飾的字段(成員變量),要求程序對該變量的訪問必須從共享內存獲取,修改必須同步刷新回共享內存,從而保證所有線程對變量訪問的可見性
  • synchronized:修飾方法或同步塊,確保同一時刻,只有一個線程處于方法或同步塊中,保證了線程對變量訪問的可見性和排他性
  • 對象、對象的監視器、同步隊列和執行線程之間的關系
    1)任意線程對由synchronized保護的object的訪問,首先要獲得object的監視器Monitor
    2)如果Monitor獲取失敗,線程進入同步隊列SynchronizedQueue,線程為BLOCKED狀態
    3)當其他獲得鎖訪問object的線程釋放鎖,該釋放操作喚醒阻塞在同步隊列中的線程,使其重新嘗試獲取object的Monitor
2.等待/通知機制
  • 生產者消費者模式
    1)線程A修改了一個對象的值,線程B在感知變化后進行相應的操作
    2)整個過程開始于線程A(生產者),最終執行于線程B(消費者)
    3)該模式隔離了“做什么”(What)和“怎么做”(How),功能層面實現了解耦
  • 原始辦法
    消費者線程不斷循環檢查信號變量是否變化,偽代碼如下,存在程序及時性和資源消耗量的兩難
while ( value != desire )  {
    Thread.sleep ( 1000 ) ;
}
doSometing();
  • 等待/通知機制 notify/wait
    1)線程A調用對象O的wait()方法進入等待狀態
    2)線程B調用對象O的notify()notifyAll()方法通知線程A
    3)線程A收到通知后從對象O的wait()方法返回,繼續執行后續操作

  • 等待/通知機制流程
    1)使用wait()notify()notifyAll()時需要先對調用對象加鎖
    2)調用wait()后,線程狀態由RUNNING變為WAITING,并將當前線程放置在等待隊列
    3)notify()notifyAll()方法調用后,等待線程依舊不會從wait()返回,需要調用notify()notifyAll()的線程釋放鎖之后,等待線程才會從wait()返回
    4)notify()/notifyAll()將等待隊列中的一個/全部等待線程從等待隊列中移到同步隊列,被移動的線程狀態由WAITING變為BLOCKED
    5)從wait()方法返回(離開同步隊列開始運行)的前提是獲得了調用對象的鎖

    Wait/Notify

  • 等待/通知的經典范式

等待方偽代碼
1.獲取對象的鎖
2.如果條件不滿足,則調用對象的wait()方法,被通知后仍要檢查條件
3.條件滿足則執行對應邏輯
synchronized(對象)  {
      while(條件不滿足)  {
            對象.wait();
      }
      對應的處理邏輯
}

通知方偽代碼
1.獲取對象的鎖
2.改變條件
3.通知所有等待在對象上的線程
synchronized(對象)  {
      改變條件
      對象.notifyAll();
}
3.管道輸入/輸出流
  • 管道IO主要用于線程間數據傳輸,傳輸媒介為內存,與文件IO或網絡IO不同
  • 主要實現類
    1)管道字節流:PipedOutputStreamPipedInputStream
    2)管道字符流:PipedReaderPipedWriter
PipedReader in = new PipedReader();
PipedWriter out = new PipedWriter();
//必須連接輸入流和輸出流,否則拋出異常
out.connect(in);
4.Thread.join()
  • 線程A使用thread.join()表示當前線程A等待thread線程終止后才從thread.join()返回
  • join()方法的源代碼邏輯結構與等待/通知經典范式一致,即加鎖、循環和處理邏輯三步
5.線程變量ThreadLocal
  • 以ThreadLocal對象為鍵、任意對象為值得存儲結構,依附于線程
  • 線程可以根據一個ThreadLocal對象查詢到綁定在這個線程上的一個值

那年離別日,只道住桐廬。桐廬人不見,今得廣州書

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容