Java 并發(fā)編程

[TOC]

進程與線程

進程是程序運行資源分配的最小單位。

進程是操作系統(tǒng)進行資源分配的最小單位,其中資源包括CPU、內(nèi)存空間、磁盤等,同一進程中的多條線程共享該進程中的全部系統(tǒng)資源,而進程和進程之間是相互獨立的。進程是具有一定獨立功能的程序關(guān)于某個數(shù)據(jù)集合上的一次運行活動,進程是系統(tǒng)進行資源分配和調(diào)度的一個獨立單位。進程分為系統(tǒng)進程和用戶進程,凡是用于完成操作系統(tǒng)的各種功能的進程就是系統(tǒng)進程;用戶進程就是所有由你啟動的進程。

線程是CPU調(diào)度的最小單位,必須依賴于進程而存在。

線程是進程的一個實體,是CPU調(diào)度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。線程自己基本上不擁有系統(tǒng)資源,只擁有一點在運行中必不可少的資源,比如程序計數(shù)器(一組寄存器和棧),但是它可與同屬一個進程的其他線程共享進程所有的全部資源。

任何一個程序都要創(chuàng)建線程,特別是Java,不管任何程序都要啟動一個main函數(shù)的主線程。JavaWeb開發(fā)里面的定時任務(wù)、定時器、JSP和Servlet、異步消息處理機制、遠程訪問接口RM等,任何一個監(jiān)聽事件等都離不開線程和并發(fā)的知識。

CPU與線程

CPU核心數(shù)與線程數(shù)的關(guān)系

目前主流CPU有雙核、三核和四核,六核也在2010年發(fā)布,增加核心數(shù)目就是為了增加線程數(shù)目,因為操作系統(tǒng)是通過線程來執(zhí)行任務(wù)的,一般情況下它們是1:1的關(guān)系,也就是說四核CPU一般擁有四個線程,但Intel引入超線程技術(shù)后,使核心數(shù)與線程數(shù)形成1:2的關(guān)系。

CPU時間片輪轉(zhuǎn)機制

我們平時在開發(fā)的時候,感覺并沒有受CPU核心數(shù)的限制,想啟動線程就啟動線程,哪怕是在單核CPU上,為什么?這就要靠CPU的時間片輪轉(zhuǎn)機制了。

時間片輪轉(zhuǎn)調(diào)度是一種最古老、最簡單、最公平且使用最廣的算法,又稱RR調(diào)度。每個進程被分配一個時間段,稱作它的時間片,即該進程允許運行的時間。當該進程的時間片運行完以后,將讓出CPU執(zhí)行權(quán),給其他進程使用。

并行與并發(fā)

并行:指應(yīng)用能夠同時執(zhí)行不同的任務(wù)。想象一條高速公路,有四個車道,那么此條高速公路可以并排行駛小于等于四輛車。CPU也是這個原理,核心數(shù)或者線程數(shù)就相當于高速公路上的車道,能同時處理的任務(wù)需要線程數(shù)或者核心數(shù)支持。

并發(fā):指應(yīng)用能夠交替執(zhí)行不同的任務(wù)。當談?wù)摬l(fā)的時候,一定要加個單位時間。比如單CPU核心下執(zhí)行多線程并非是同時執(zhí)行多個任務(wù),而是CPU以不可察覺到的速度在進行時間片輪轉(zhuǎn)。

Java里的線程

Java是一門支持多線程的語言,運行中的Java程序至少有一個主線程。

啟動線程的方式

  1. 繼承Thread
public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("執(zhí)行線程--------------");
    }
}

public static void main(String[] args) {
     MyThread myThread = new MyThread();
     myThread.start();
}
  1. 實現(xiàn)Runnable(實現(xiàn)Callable,這種方式其實可以看做是實現(xiàn)Runnable,不過它能拿到異步的返回結(jié)果)
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("執(zhí)行線程--------------");
    }
}

public static void main(String[] args) {
    Thread thread = new Thread(new MyRunnable());
    thread.start();
}
public class MyCallable implements Callable {
    @Override
    public Object call() throws Exception {
        System.out.println("執(zhí)行線程--------------");
        return 1;
    }
}

public static void main(String[] args) {
    //FutureTask實現(xiàn)了RunnableFuture,RunnableFuture實現(xiàn)了Runnable,所以可以看做是實現(xiàn)Runnable啟動線程。
    FutureTask<Callable> future = new FutureTask(new MyCallable());
    Thread thread = new Thread(future);
    thread.start();
}

線程的結(jié)束

  1. run方法執(zhí)行完

  2. run方法中拋出了一個未處理的異常導(dǎo)致線程結(jié)束。

線程的方法

interrupt():對線程進行中斷操作,將會設(shè)置線程的中斷狀態(tài)為true,至于中斷后線程是死亡,還是等待,還是繼續(xù)運行,取決于線程本身。線程會時不時的檢測這個中斷標志位,以判斷線程是否應(yīng)該中斷。更確切地說,如果線程被wait,join和sleep三種方法之一阻塞,此時調(diào)用interrupt,那么該線程將拋出一個InterrputedException中斷異常,從而提早地終結(jié)被阻塞狀態(tài)。如果線程沒有被阻塞,這時調(diào)用interrupt不會起作用,直到執(zhí)行wait,join,sleep時才馬上拋出InterrputedException。

Thread.interrupted():靜態(tài)方法,內(nèi)部實現(xiàn)是調(diào)用isInterrupted(),并且會重置當前線程的中斷狀態(tài)。

isInterrupted():實例方法,不會重置當前線程的中斷狀態(tài)。作用與Thread.interrupted()相同,獲取調(diào)用線程的中斷狀態(tài)。

yield():靜態(tài)方法,使當前線程讓出CPU占有權(quán),但讓出的時間是不可設(shè)定的,也不會釋放鎖資源。所有執(zhí)行yield()的線程有可能在進入到可執(zhí)行態(tài)后馬上又被執(zhí)行。

join():實例方法,把指定線程加入到當前線程,可以將兩個交替執(zhí)行的線程合并為順序執(zhí)行的線程。比如線程A中調(diào)用了線程B的join(),直到線程B執(zhí)行完后才會繼續(xù)執(zhí)行線程A。

線程的生命周期

線程生命周期.png

線程的狀態(tài)

初始(NEW):新創(chuàng)建了一個線程對象,還沒有調(diào)用start()。

運行(RUNNABLE):Java線程中將就緒(READY)和運行中(RUNNING)兩種狀態(tài)統(tǒng)稱為運行。就緒中的線程在獲取到CPU時間片后進入運行中。

阻塞(BLOCKED):表示線程阻塞于鎖。

等待(WATING):進入該狀態(tài)的線程需要等待其他線程做出一些特定動作(通知或中斷)。

超時等待(TIMED_WATING):該狀態(tài)不同于WATING,它可以在指定時間后自行返回。

終止(TERMINATED):表示該線程已經(jīng)執(zhí)行完畢。

典型線程問題

  1. 死鎖

    兩個或兩個以上的線程在執(zhí)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞現(xiàn)象,若無外力作用,它們都將無法推進下去。此時稱系統(tǒng)處于死鎖狀態(tài)或者說產(chǎn)生了死鎖。

    死鎖發(fā)生的條件:

    1). 互斥條件:指線程對所分配的資源進行排他性使用,即在一段時間內(nèi)某資源只由一個線程占用,如果此時還有其他線程請求該資源,則請求者只能等待,直至占有該資源的線程釋放資源。

    2). 請求和保持條件:指線程已經(jīng)至少保持一個資源,但又提出了新的資源請求,而該資源已經(jīng)被其他線程占有,此時請求線程阻塞,但又對自己已獲得的其他資源保持不釋放。

    3). 不剝奪條件:指線程已獲得資源,在未使用完之前,不能被剝奪,只能在使用完后由自己釋放。

    4). 環(huán)路等待條件:指在發(fā)生死鎖時,必然存在一個線程與資源的環(huán)形鏈,即線程P0正在等待P1占用的資源,P1正在等待P2占用的資源,P2正在等待Pn占用的資源,而Pn則正在等待P1占用的資源。

    解決死鎖的方案

    1). 內(nèi)部通過順序比較,確定拿鎖的順序。

    2). 采用嘗試拿鎖的機制。

  2. 活鎖

    兩個線程在嘗試拿鎖的機制中,發(fā)生多個線程之間互相謙讓,同一個線程總是拿到同一把鎖,當這個線程繼續(xù)往下執(zhí)行想要拿到另一把鎖時,卻因為拿不到而釋放第一把鎖,下次還是這個線程拿到了第一把鎖,卻又不能往下執(zhí)行,如此周而復(fù)始,導(dǎo)致線程并未阻塞,但是兩個線程都不會往下執(zhí)行了。

    解決辦法:每個線程休眠隨機的時間,錯開拿鎖時間。

  3. 線程饑餓

    低優(yōu)先級的線程總是拿不到CPU執(zhí)行時間。

ThreadLocal詳解

?著作權(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)容

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