Java多線程--線程及相關(guān)的Java API

Java多線程--線程及相關(guān)的Java API

線程與進(jìn)程

進(jìn)程是線程的容器,程序是指令、數(shù)據(jù)的組織形式,進(jìn)程是程序的實(shí)體。

一個(gè)進(jìn)程中可以容納若干個(gè)線程,線程是輕量級(jí)的進(jìn)程,是程序執(zhí)行的最小單位。我們研究多線程而不是多進(jìn)程,因?yàn)榫€程之間的切換與調(diào)度的成本遠(yuǎn)小于進(jìn)程。

線程的生命周期

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WATING,
    TERMINATED;
}

線程的所有狀態(tài)都在枚舉類State中定義了,其中:

NEW表示剛剛創(chuàng)建的線程,此時(shí)線程還沒有開始執(zhí)行。調(diào)用start()后線程進(jìn)入RUNNABLE狀態(tài),線程在執(zhí)行過程中遇到synchronized同步塊,就進(jìn)入BLOCKED阻塞狀態(tài),此時(shí)線程暫停執(zhí)行,直到獲得請(qǐng)求的鎖。WAITING和TIMED_WAITING都表示等待,區(qū)別是前者是無時(shí)間限制的等待,后者是有時(shí)限的等待。等待可以是執(zhí)行wait()方法后等待notify()方法將其喚醒,也可以是通過join()方法等待的線程等待目標(biāo)線程的執(zhí)行結(jié)束。一旦等待了期望事件,線程再次執(zhí)行,從等待狀態(tài)變成RUNNABLE狀態(tài)。線程執(zhí)行結(jié)束后,進(jìn)入TERMINATED狀態(tài)。

Java中的線程API

線程的新建

Thread t = new Thread();
t.start();

如上就創(chuàng)建了一個(gè)線程,要想這個(gè)線程創(chuàng)建后做點(diǎn)什么,需要覆寫Thread類中的run()方法。

Thread t = new Thread();
t.run();

不能使用上面的方式啟動(dòng)線程,這樣調(diào)用不會(huì)產(chǎn)生任何新的線程,只是一個(gè)單純的方法調(diào)用。要想開啟線程,不能使用run(),而應(yīng)該用start()。

還可以通過實(shí)現(xiàn)Runnable接口,覆寫接口中的run()方法,然后在Thread的構(gòu)造函數(shù)中傳入Runnale,此方法更為常用。

new Thread(new Runnale() {
    @Override
    public void run() {}
}).start();

由于run()是Runnable接口中的唯一方法,在Java8種可以簡單寫成:

new Thread(() -> {}).start();

線程的終止

Thread類中有個(gè)stop()方法,但是已經(jīng)被廢棄。該方法太過暴力,強(qiáng)行把執(zhí)行到一半的線程終止掉,可能會(huì)引起數(shù)據(jù)不一致的問題。stop()方法會(huì)直接終止線程,并立即釋放這個(gè)線程持有的鎖,而鎖恰好是用來維持對(duì)象的一致性的。

舉個(gè)例子User類中有id和name兩個(gè)字段,一開始id = 1, name = "ming", 我們要set數(shù)據(jù)id = 2, name = "jun"更新user,若剛寫入了id = 2,name還沒來得及寫入就stop()的話,立即釋放鎖,因此其他線程得到鎖讀取這個(gè)對(duì)象的話,就會(huì)得到id = 2, name = "ming",這不是我們想要的結(jié)果,數(shù)據(jù)已經(jīng)被寫壞了!

那么要如何終止線程呢?可以自定義退出線程的方法,比如設(shè)置標(biāo)志位

volatile boolean stoped;
public void myStop() {
    stoped = true;
}

在線程合適的地方, 通過判斷stoped變量來手動(dòng)退出線程。

@Override
public void run() {
    while (true) {
        if (stoped) {
            break;
        }
    }
    // synchronized (object) {do sth.}
}

線程的中斷

線程中斷不會(huì)使線程立即退出,而是通知目標(biāo)線程“你應(yīng)該退出了”,退不退出是由目標(biāo)線程來決定的。

Thread類有個(gè)實(shí)例interrupt()方法,它通知目標(biāo)線程中斷,也就是設(shè)置中斷標(biāo)志位。

另一個(gè)實(shí)例方法isInterrupt()用于判斷當(dāng)前線程是否被中斷(通過檢查中斷標(biāo)志位);

靜態(tài)方法Thread.interrupted()也可以判斷當(dāng)前線程的中斷狀態(tài),但同時(shí)會(huì)清除當(dāng)前線程的中斷標(biāo)志位。

wait()和notify()

wait()是Object的方法,也就是說每個(gè)對(duì)象都能調(diào)用。在當(dāng)前線程中某個(gè)對(duì)象調(diào)用了wait()方法則該線程就停止執(zhí)行,轉(zhuǎn)為等待狀態(tài)。舉個(gè)例子,在線程T中,調(diào)用了obj.wait(),線程T就進(jìn)入了對(duì)象obj的等待隊(duì)列,則線程T就在對(duì)象obj上等待,一直等到其他線程調(diào)用了obj.notify(),這時(shí)obj從它的等待隊(duì)列中隨機(jī)喚醒一個(gè)線程(不一定是剛才的線程T)。所以notify()方法喚醒的選擇不是公平的,不是說先等待的線程就一定會(huì)先被喚醒。

Object類還有一個(gè)notifyAll()方法,會(huì)喚醒某對(duì)象等待隊(duì)列中的所有線程。

notify()wait()方法在執(zhí)行前必須獲得對(duì)象的鎖。為此在synchronized (obj)中先獲得了鎖,才能調(diào)用wait()或者notify()方法,wait()會(huì)立刻釋放鎖,而notify()不會(huì)立刻釋放鎖,wait()狀態(tài)的線程也不能立刻獲得鎖;等到執(zhí)行notify()的線程退出同步塊后,才釋放鎖,此時(shí)其他處于wait()狀態(tài)的線程才能獲得該鎖。

線程的掛起和繼續(xù)執(zhí)行

Thread類中還有兩個(gè)已經(jīng)被廢棄的方法,分別是suspend()resume()。

suspend()在導(dǎo)致線程暫停的同時(shí),不會(huì)釋放任何鎖,而且線程狀態(tài)還是Runnable,其他任何線程都得不到被該線程占用的鎖,直到在線程上執(zhí)行了resume(),被掛起的線程才能繼續(xù),其他阻塞在相關(guān)鎖上的線程也才可以繼續(xù)執(zhí)行。但是如如resume()不小心在suspend()之前調(diào)用了,可能造成線程被永久掛起,從而永久占用鎖。

注意和notify()、wait()方法的區(qū)別。由于方法已被廢棄,不再討論更多。

join()和yield()

join()表示將線程“加入”到當(dāng)前線程中,會(huì)一直阻塞當(dāng)前線程,直到該線程執(zhí)行完畢,或者說:當(dāng)前線程一直等待join入的線程執(zhí)行完畢后,才能繼續(xù)執(zhí)行。

public class Abc {
    public static volatile int i;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {for (i = 0; i < 100; i++);});
        t.start();
        t.join();
        System.out.println(i);
    }
}

舉個(gè)例子,上面的代碼中有main線程和t線程,由于線程t的join,使得main線程中System.out.println(i)的一定是等待t線程執(zhí)行完畢后才能執(zhí)行,因此打印值一定是100.

join()的實(shí)現(xiàn)依賴于wait()。A線程中調(diào)用了B線程的join方法,則相當(dāng)于A線程調(diào)用了B線程的wait方法,在調(diào)用了B線程的wait方法后,A線程就會(huì)進(jìn)入阻塞狀態(tài)。所以A線程會(huì)等待B執(zhí)行完畢后才能繼續(xù)執(zhí)行。

yield()表示主動(dòng)禮讓當(dāng)前線程的CPU使用權(quán),讓出不是會(huì)不會(huì)回來參與CPU資源的爭奪,下次還能被分配到。調(diào)用該方法,好比是當(dāng)前線程說:我的工作暫時(shí)做完,給其他線程一些工作機(jī)會(huì)。

volatile關(guān)鍵字

volatile可以確保變量的可見性、有序性,不保證復(fù)雜操作的原子性。所以volatite對(duì)于原子性操作只能說有一定的幫助,但不能取代鎖。

當(dāng)用volatile修飾一個(gè)變量時(shí),相當(dāng)于告訴虛擬機(jī),這個(gè)變成極有可能被某些線程修改,確保該變量在被修改后,其他線程能“看到”該變量已經(jīng)被改變。

synchronized

上面說了volatile不能真正保證線程安全,只能保證一個(gè)線程修改了數(shù)據(jù)后,其他線程能看到這個(gè)改變。兩個(gè)線程同時(shí)修改同一個(gè)共享變量時(shí),仍然會(huì)產(chǎn)生沖突。

為了保證操作的原子性,可以使用同步塊,synchronized

被synchronized修飾的方法,或被其包起來的代碼段,任何時(shí)候只有一個(gè)線程可以執(zhí)行。這就避免了多個(gè)線程修改同一個(gè)數(shù)據(jù)的危險(xiǎn)。synchronized可以實(shí)現(xiàn)線程間的同步,主要是對(duì)被同步的代碼加鎖。

Dog dog = new Dog();

  • 指定的加鎖對(duì)象,如synchronized (dog) {}或者synchronized (Dog.class) {}
  • 作用于實(shí)例方法上,如public synchronized void fun() {},此時(shí)使用的鎖是當(dāng)前實(shí)例。
  • 作用于靜態(tài)方法,如public static synchronized void fun() {},此時(shí)使用的鎖是當(dāng)前。

注意:為了保證線程間同步,使得在多個(gè)線程間修改同一個(gè)數(shù)據(jù)時(shí),保證任何時(shí)候只有一個(gè)線程能修改。多個(gè)線程需要使用同一把鎖。

因?yàn)閟ynchronized中的代碼任何時(shí)候只能有一個(gè)線程在執(zhí)行它,所以可以說被synchronized限制的多個(gè)線程是串行執(zhí)行的。

線程的其他概念

線程組

ThreadGroup類可以容納多個(gè)線程,可以將多個(gè)功能相同的線程放入線程組中,便于管理。

守護(hù)線程(Daemon)

守護(hù)線程用于守護(hù)用戶進(jìn)程。守護(hù)進(jìn)程在后臺(tái)默默完成一些系統(tǒng)性的服務(wù),比如垃圾回收線程、JIT線程就可理解成守護(hù)線程。而用戶線程是用于完成程序的業(yè)務(wù)操作的,當(dāng)程序中的所有用戶線程執(zhí)行完畢,守護(hù)線程沒有了可守護(hù)的對(duì)象,程序自然就退出。即:在一個(gè)Java程序中只有守護(hù)線程時(shí),虛擬機(jī)就自然退出。

public static void main(String[] args) {
    Thread t = new Thread(() -> {
        while (true) {
        }
    });
    t.setDaemon(true);
    t.start();
    System.out.println("over");
}

在上面的代碼中,線程t被設(shè)置成守護(hù)線程(必須在start()之前調(diào)用),用戶線程main在打印"over"后執(zhí)行完畢,t沒有要守護(hù)的線程了,所以程序會(huì)出,如果不設(shè)置t為守護(hù)線程,我們知道由于while (true)程序永遠(yuǎn)不會(huì)退出。

線程的優(yōu)先級(jí)

Java的線程有優(yōu)先級(jí),高優(yōu)先級(jí)線程在競爭資源時(shí)更有優(yōu)勢,但這也是個(gè)概率問題,高優(yōu)先級(jí)也可能搶占失敗,低優(yōu)先級(jí)線程也不是說一定會(huì)饑餓。

Java中的線程有1~10的優(yōu)先級(jí),數(shù)字越大,優(yōu)先級(jí)越高。Thread類中內(nèi)置了三個(gè)默認(rèn)的優(yōu)先級(jí),如下。

    public final static int MIN_PRIORITY = 1;
    public final static int NORM_PRIORITY = 5;
    public final static int MAX_PRIORITY = 10;

by @sunhaiyu

2018.4.13

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

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

  • 該文章轉(zhuǎn)自:http://blog.csdn.net/evankaka/article/details/44153...
    加來依藍(lán)閱讀 7,469評(píng)論 3 87
  • 本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等。 首先講...
    李欣陽閱讀 2,597評(píng)論 1 15
  • Java多線程學(xué)習(xí) [-] 一擴(kuò)展javalangThread類 二實(shí)現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 3,107評(píng)論 1 18
  • 寫在前面的話: 這篇博客是我從這里“轉(zhuǎn)載”的,為什么轉(zhuǎn)載兩個(gè)字加“”呢?因?yàn)檫@絕不是簡單的復(fù)制粘貼,我花了五六個(gè)小...
    SmartSean閱讀 4,945評(píng)論 12 45
  • 畫了兩個(gè)周末才完成的草莓芝士蛋糕,哈哈 遠(yuǎn)看有點(diǎn)像那么回事,當(dāng)然啦,小白發(fā)現(xiàn)還是有很多的待改進(jìn)地方,繼續(xù)加油! 自...
    排骨要增肥__閱讀 508評(píng)論 10 15

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