線程基本知識

進程與線程的區(qū)別

  • 進程:是系統(tǒng)進行分配和管理資源的基本單位
  • 線程:進程的一個執(zhí)行單元,是進程內(nèi)調(diào)度的實體、是CPU調(diào)度和分派的基本單位,是比進程更小的獨立運行的基本單位。線程也被稱為輕量級進程,線程是程序執(zhí)行的最小單位。
  • 一個程序至少一個進程,一個進程至少一個線程。
  • 進程有自己的獨立地址空間,每啟動一個進程,系統(tǒng)就會為它分配地址空間,建立數(shù)據(jù)表來維護代碼段、堆棧段和數(shù)據(jù)段,這種操作非常昂貴。 而線程是共享進程中的數(shù)據(jù)的,使用相同的地址空間,因此CPU切換一個線程的花費遠比進程要小很多,同時創(chuàng)建一個線程的開銷也比進程要小很多。 線程之間的通信更方便,同一進程下的線程共享全局變量、靜態(tài)變量等數(shù)據(jù),而進程之間的通信需要以通信的方式進行。 如何處理好同步與互斥是編寫多線程程序的難點。 多進程程序更健壯,進程有獨立的地址空間,一個進程崩潰后,在保護模式下不會對其它進程產(chǎn)生影響, 而線程只是一個進程中的不同執(zhí)行路徑。線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間,所以可能一個線程出現(xiàn)問題,進而導(dǎo)致整個程序出現(xiàn)問題

線程的狀態(tài)及其相互轉(zhuǎn)換

初始(NEW):新創(chuàng)建了一個線程對象,但還沒有調(diào)用start()方法。
運行(RUNNABLE):處于可運行狀態(tài)的線程正在JVM中執(zhí)行,但它可能正在等待來自操作系統(tǒng)的其他資源,例如處理器。
阻塞(BLOCKED):線程阻塞于synchronized鎖,等待獲取synchronized鎖的狀態(tài)。
等待(WAITING):Object.wait()、join()、 LockSupport.park(),進入該狀態(tài)的線程需要等待其他線程做出一些特定動作(通知或中斷)。
超時等待(TIME_WAITING):Object.wait(long)、Thread.join()、LockSupport.parkNanos()、LockSupport.parkUntil,該狀態(tài)不同于WAITING,它可以在指定的時間內(nèi)自行返回。
終止(TERMINATED):表示該線程已經(jīng)執(zhí)行完畢自己結(jié)束,或者產(chǎn)生了異常而結(jié)束。

創(chuàng)建線程的方式

  • 繼承Thread,并重寫父類的run方法
  • 實現(xiàn)Runable接口,并實現(xiàn)run方法
  • 使用匿名內(nèi)部類
  • Lambda表達式
  • 線程池

什么是線程安全性

當(dāng)多個線程訪問某個類,不管運行時環(huán)境采用何種調(diào)度方式或者這些線程如何交替執(zhí)行,并且在主調(diào)代碼中不需要任何額外的同步或協(xié)同,這個類都能表現(xiàn)出正確的行為,那么就稱這個類為線程安全的。----《并發(fā)編程實戰(zhàn)》

  • 什么是線程不安全?
    多線程并發(fā)訪問時,得不到正確的結(jié)果。

原子性操作

一個操作或者多個操作 要么全部執(zhí)行并且執(zhí)行的過程不會被任何因素打斷,要么就都不執(zhí)行。
volatile關(guān)鍵字僅僅保證可見性,并不保證原子性 ? synchronize關(guān)機字,使得操作具有原子性。

synchronized關(guān)鍵字

  • 內(nèi)置鎖

每個java對象都可以用做一個實現(xiàn)同步的鎖,這些鎖稱為內(nèi)置鎖。線程進入同步代碼塊或方法的時候會自動獲得該鎖,在退出同步代碼塊或方法時會釋放該鎖。獲得內(nèi)置鎖的唯一途徑就是進入這個鎖的保護的同步代碼塊或方法。

  • 互斥鎖

內(nèi)置鎖是一個互斥鎖,這就是意味著最多只有一個線程能夠獲得該鎖,當(dāng)線程A嘗試去獲得線程B持有的內(nèi)置鎖時,線程A必須等待或者阻塞,直到線程B釋放這個鎖,如果B線程不釋放這個鎖,那么A線程將永遠等待下去。

  • 修飾普通方法:鎖住對象的實例
  • 修飾代碼塊: 鎖住一個對象 synchronized (lock) 即synchronized后面括號里的內(nèi)容
  • 修飾Class: 鎖住整個類
  • 修飾靜態(tài)方法:鎖住整個類
    當(dāng)作用于靜態(tài)方法時,鎖住的是Class實例,又因為Class的相關(guān)數(shù)據(jù)存儲在永久帶PermGen(jdk1.8則是metaspace),永久帶是全局共享的,因此靜態(tài)方法鎖相當(dāng)于類的一個全局鎖,會鎖所有調(diào)用該方法的線程;
//放在方法 鎖得是對象 和 synchronized(this)是一樣的
    public synchronized void lockMethod(){
        System.out.println(Thread.currentThread().getName()+" lockMethod");
    }
    
    public void lockMethod_CodeBlockByThis() {
        //代碼塊 this鎖的是對象
        synchronized(this) {
            System.out.println(Thread.currentThread().getName()+" lockMethod_CodeBlockByThis");
        }
    }

    //類鎖
    public void lockMethod_CodeBlockByClass() {
        //代碼塊 Class鎖的是類,無論任何地方,任何類只要是鎖的是同一個class,并發(fā)時都會等待
        synchronized(ThreadDemo.class) {
            System.out.println(Thread.currentThread().getName()+"lockMethod_CodeBlockByClass");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //放在靜態(tài)方法上,鎖的是當(dāng)前的class,此處的鎖和synchronized(ClassA.class)是同樣的
    public synchronized static void lockStaticMethod() {
        System.out.println(Thread.currentThread().getName()+" lockStaticMethod");

    }

volatile關(guān)鍵字及其使用場景


指令重排:CPU和編譯器為了提升程序執(zhí)行的效率,會按照一定的規(guī)則允許進行指令優(yōu)化,在某些情況下,這種優(yōu)化會帶來一些執(zhí)行的邏輯問題,主要的原因是代碼邏輯之間是存在一定的先后順序,在并發(fā)執(zhí)行情況下,會發(fā)生二義性,即按照不同的執(zhí)行邏輯,會得到不同的結(jié)果信息。


  • 能且僅能修飾變量
  • 保證該變量的可見性,volatile關(guān)鍵字僅僅保證可見性,并不保證原子性
  • 禁止指令重排序
  • A、B兩個線程同時讀取volatile關(guān)鍵字修飾的對象,A讀取之后,修改了變量的值,修改后的值,對B線程來說,是可見
  • 使用場景 1:作為線程開關(guān) 2:單例,修飾對象實例,禁止指令重排序

單例與線程安全

  • 餓漢式--本身線程安全
    在類加載的時候,就已經(jīng)進行實例化,無論之后用不用到。如果該類比較占內(nèi)存,之后又沒用到,就白白浪費了資源。
public class HungerSingleton {

    private static HungerSingleton ourInstance = new HungerSingleton();

    public static HungerSingleton getInstance() {
        return ourInstance;
    }

    private HungerSingleton() {
    }

}
  • 懶漢式 -- 最簡單的寫法是非線程安全的
    在需要的時候再實例化
public class LazySingleton {

    private static volatile LazySingleton ourInstance = null;

    public static LazySingleton getInstance() {
        if(null == ourInstance){
            synchronized (ourInstance){
                if(ourInstance == null){
                    ourInstance = new LazySingleton();
                }
            }
        }
        return ourInstance;
    }

    private LazySingleton() {
    }
}

如何保證單例?
private私有的空參構(gòu)造器
static的對象和方法getInstance

如何避免線程安全性問題

  • 線程安全性問題成因
     多線程環(huán)境
     多個線程操作同一共享資源
     對該共享資源進行了非原子性操作
    
    • 如何避免

    打破成因中三點任意一點
    1:多線程環(huán)境--將多線程改單線程(必要的代碼,加鎖訪問)
    2:多個線程操作同一共享資源--不共享資源(ThreadLocal、不共享、操作無狀態(tài)化、不可變)
    3:對該共享資源進行了非原子性操作-- 將非原子性操作改成原子性操作(加鎖、使用JDK自帶的原子性操作
    的類、JUC提供的相應(yīng)的并發(fā)工具類)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 九種基本數(shù)據(jù)類型的大小,以及他們的封裝類。(1)九種基本數(shù)據(jù)類型和封裝類 (2)自動裝箱和自動拆箱 什么是自動裝箱...
    關(guān)瑋琳linSir閱讀 2,064評論 0 47
  • 請帶著如下問題閱讀本文。1.什么是線程2.線程和進程的區(qū)別3.多線程的優(yōu)缺點4.什么是多線程的上下文切換5.wai...
    西部小籠包閱讀 311評論 1 0
  • Java-Review-Note——4.多線程 標簽: JavaStudy PS:本來是分開三篇的,后來想想還是整...
    coder_pig閱讀 1,772評論 2 17
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,674評論 1 32
  • 以上代碼會重復(fù)運行 , 不會停止。 JMM(java內(nèi)存模型) 若想學(xué)習(xí)好多線程, 那么必須了解一下JMM Jav...
    尼爾君閱讀 1,827評論 0 2

友情鏈接更多精彩內(nèi)容