深入淺出java多線程編程


本文將從以下幾個方面描述java多線程編程相關的內容。

  • 線程簡介
  • 線程的狀態與上下文切換的概念
  • 線程的監控
  • synchronize和volatile
  • 多線程的優點和缺點
  • 多線程的設計模式
  • 線程池
  • 線程簡介

  進程代表運行中的程序。一個運行的java程序就是一個進程。

  從操作系統的角度來看,線程是進程中可獨立執行的子任務。一個進程可以包含多個線程,同一個進程中的線程共享該進程所申請到的資源,如內存空間和文件句柄等。

  從JVM的角度來看,線程是進程中的一個組件,它可以看作執行java代碼的最小單位。

  java中的線程可以分為守護線程和用戶線程。用戶線程會組織jvm的正常停止,即jvm正常停止前應用程序中的所有用戶線程必須先停止完畢,否則jvm無法停止。而守護線程則不會影響jvm的正常停止。

  • 線程的狀態與上下文切換的概念

  java線程的狀態可以通過調用相應thread的getState方法獲取。該方法的返回值類型Thread.State是一個枚舉類型,包含的狀態有以下幾種。

  1. NEW
    1. 一個剛創建而未啟動的線程處於該狀態。由於一個線程實例只能被啟動一次,因此一個線程只可能有一次處於該狀態。  
  2. RUNNABLE
    1. 這是一個復合狀態,包括READY和RUNNING。
    2. READY。表示該狀態的線程可以被jvm的線程調度器進行調度而使之處於RUNNING狀態。
    3. RUNNING。表示該線程正在運行,即相應線程的run方法正在被執行。當Thread實例的yield方法被調用時或由於線程調度器的原因,相應線程的狀態會由RUNNING轉為READY。  
  3. BLOCKED
    1. 一個線程發起了一個阻塞式io操作后,或者試圖去獲取以一個由其他線程持有的鎖時,相應的線程會處於該狀態。處於該狀態的線程並不會占用CPU資源。當相應的io操作完成后,或者相應的鎖被其他線程釋放后,該線程的狀態又可以轉換為RUNNABLE。
  4. WAITING
    1. 一個線程執行了某些方法調用之后就會處於這種無限等待其他線程執行特定操作的狀態。這些方法包括:Object.wait(),Thread.join()...能使相應線程從WAITING轉換到RUNNABLE的相應方法包括:Object.notify(),Object.notifyAll()...
  5. TIMED_WAITING
    1. 與WAITING狀態類似,差別在於等待時間非無限等待,指定時間過后,自動轉為RUNNABLE。
  6. TERMINATED
    1. 已經執行結束的線程處於該狀態。同NEW一樣,有且僅有一次。run方法正常返回或者由於異常終止都會導致該狀態。
  7. 上下文切換
    1. 由上述描述可知,一個線程的生命周期中,只可能一次處於NEW和TERMINATED狀態。而一個線程的狀態從RUNNABLE轉換為BLOCKED,WAITING和TIME_WAITING狀態中的任意一個都意味着上下文切換。
    2. 上下文切換的場景類似於我們接聽手機,我們正在打電話時有另一個電話打進來,我們接聽新的電話,而之前的電話就處於等待狀態,等新的電話結束之后,我們回過頭來與前者重新通話,“我們之前說道哪兒了?”
    3. 線程間的切換,狀態變化需要對相應的上下文信息進行保存和恢復,這個過程就被稱為上下文切換。
    4. 上下文切換會帶來額外的開銷,包括保存和恢復線程上下文信息的開銷、對線程進行調度的CPU時間開銷以及CPU緩存內容失效的開銷。
    5. Linux平台下,我們可以使用perf命令來監視上下文切換情況。
    6. Window平台下,我們可以使用Window自帶工具perfmon來監視上下文切換情況。
  • 線程的監控
    • jvisualvm、jstack、JMC
  • synchronized和volatile
    • 了解這兩個關鍵字之前,我們需要先有以下幾個概念,原子性、內存可見性和重排序。
    • 原子性。原子操作是指相應的操作是單一不可分割的操作。例如:count++就不是原子操作,因為該操作分為三步,1)讀取count的值,2)count做++運算,3)把運算后的值賦予count。在多線程環境下,該操作可能會收到其他線程的干擾,導致我們不能得到想要的結果。
    • 內存可見性。CPU在執行代碼的時候,為了減少變量訪問的時間消耗,可能會將代碼中訪問的變量的值緩存到該CPU的緩存區。因此代碼訪問或者寫入的變量,可能只是在緩存區而不是主內存。這就導致了一個CPU對變量的操作可能無法被其他CPU感知。
    • 重排序。編譯器和CPU為了提高指令的執行效率可能會進行指令重排序,意思是一段代碼的實際執行順序會被重新排序。例如:People p = new People();正常地執行流程為:1)創建People的實例,2)將實例賦予變量p。但是由於指令重排的作用,實際實行順序可能是:1)分配一段用於儲存People實例的內存空間,2)將對該空間的引用賦值給變量p,3)創建People的實例。因此,當其他線程訪問變量p時,可能此時p實例的初始化尚未完成。
    • synchronized關鍵字實現操作原子性的本質是通過該關鍵字所包括的臨界區的排他性保證在同一時刻只有一個線程能執行臨界區中的代碼。該操作保證了原子性和內存可見性。
    • volatile關鍵字保證了內存可見性,即,一個線程對一個volatile關鍵字修飾的變量的值的更改對於其他訪問該變量的線程總是可見的。其核心機制為當一個線程更改了volatile關鍵字修飾的變量的值時,該值會被寫入主內存而不僅僅時該線程的CPU緩存區,而其他CPU的緩存區中儲存的該變量的值就會失效。這就保證了當任意線程訪問一個volatile修飾的值時,那一刻得到的值一定是最新的。但是如果在讀取后,有線程對其進行了修改,就無法保證操作的原子性了。volatile關鍵字的另一個作用是它禁止了指令重排序。
    • synchronized關鍵字技能保證操作的原子性,也能保證內存可見性,但是會導致上下文切換。volatile關鍵字僅能保證內存可見性。
  • 多線程的優點和缺點
    • 優點
    • 提高系統的吞吐量。
    • 提高響應性。一個慢的操作不會影響其他請求的處理。
    • 充分利用多核CPU資源。
    • 最小化對系統資源的使用。一個進程中的多個線程可以共享該進程申請的資源(如內存空間)。
    • 簡化程序的結構。
    • 缺點
    • 線程安全問題。多個線程共享數據必然會導致復雜度上升。
    • 線程的生命特征問題。多個線程在交互的過程中會出現無法充分使用線程的生命周期的問題,會導致一定程度上的浪費。
    • 上下文切換問題。頻繁的上下文切換會增加對系統的消耗,不利於系統的吞吐量。
    • 可靠性問題。如果一個進程由於某種意外中止了,那么里面所有的線程都無法繼續運行。
  • 多線程的設計模式
    • 多線程設計模式所解決的問題可以分為以下幾類:
      • 不使用鎖的情況下保證線程安全
      • 優雅的停止線程
      • 線程協作
      • 提高並發性
      • 提高響應性
      • 減少資源消耗
  • 線程池
    • 本來想自己整理,網上看到一篇博客關於線程池也相當細致,就不重復造輪子了,大家可以直接轉鏈接:https://www.cnblogs.com/superfj/p/7544971.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM