[Java并發(fā)]-----第1章 并發(fā)編程線程基礎(chǔ)

透徹理解Java并發(fā)編程

1. 什么是線程

? 進(jìn)程是代碼在數(shù)據(jù)集合上的一次運(yùn)行活動(dòng),是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位.線程則是進(jìn)程的一個(gè)執(zhí)行路徑,一個(gè)進(jìn)程中至少一個(gè)線程.進(jìn)程中的多個(gè)線程共享進(jìn)程的資源.

? 操作系統(tǒng)在分配資源時(shí)是把資源分配給進(jìn)程的,但是CPU資源是被分配給線程的.

? 一個(gè)進(jìn)程中有多個(gè)線程,多個(gè)線程共享進(jìn)程的==堆==(存放對(duì)象實(shí)例)和==方法區(qū)==(類,常量和靜態(tài)變量),每個(gè)線程有自己的==程序計(jì)數(shù)器==(記錄下一步要執(zhí)行的指令地址,用于記錄當(dāng)前線程執(zhí)行的位置)和==棧==區(qū)域(儲(chǔ)存該項(xiàng)成的局部變量,和方法調(diào)用幀棧).

2. 線程創(chuàng)建與運(yùn)行

? Java有三種創(chuàng)建線程的方式,分別為==實(shí)現(xiàn)Runnable接口的Run方法==(將接口對(duì)象傳入Thread對(duì)象,運(yùn)行Thread對(duì)象的start()方法),==繼承Thread類==(執(zhí)行start()方法)和==FutureTask方式==(實(shí)現(xiàn)Callable<E>接口,泛型E為線程返回值的類型,將接口實(shí)例對(duì)象傳入FutrueTask對(duì)象,再將FutrueTask對(duì)象傳入Thread對(duì)象,執(zhí)行start方法).

FutureTask方式:

public class CallerTask implements Callable<String>{

        @Override
        public String call() throws Exception {
            System.out.println("child thread");
            return "hello";
        }

        public static void main(String[] args) {
            FutureTask<String> futureTask = new FutureTask<==wait()方法在掛起該線程的同時(shí)釋放該線程的監(jiān)視器鎖>(new CallerTask());
            new Thread(futureTask).start();
            try{
                String result = futureTask.get();
                System.out.println(result);
            } catch (ExecutionException | InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

? 其中實(shí)現(xiàn)Runnable接口的方法可以實(shí)現(xiàn)方法重用(接口中的方法不占多分空間,而多個(gè)Thread子類對(duì)象的每一個(gè)Run()方法都需要占用堆內(nèi)存).

==優(yōu)缺點(diǎn):==

  • ==繼承Thread類:方便傳參(成員變量,get(),set()方法)==
  • ==實(shí)現(xiàn)Runnable接口:沒(méi)有單繼承的限制==
  • ==FutureTask方法:線程的運(yùn)行可以有返回值.==

3. 線程通知與等待

(1). wait()函數(shù)

? 當(dāng)一個(gè)線程調(diào)用了某個(gè)對(duì)象的wait()方法后,這個(gè)線程會(huì)被阻塞掛起.

  1. 只有當(dāng)其他線程調(diào)用了==此對(duì)象==的notify()或notifyAll()方法,該線程才能被返回
  2. 當(dāng)其他線程調(diào)用了==該線程==的interrupt()方法,該線程拋出異常返回

? ==要使用某對(duì)象wait()函數(shù),就必須實(shí)現(xiàn)得到了該對(duì)象的監(jiān)視器鎖==(也就是之前的線程鎖),有兩種途徑:1.當(dāng)前在該對(duì)象的==同步方法==(成員方法)中. 2.在該對(duì)象的==同步代碼塊==中.那就意味這,在調(diào)用wait()方法時(shí),當(dāng)前線程一定是被這個(gè)對(duì)象的監(jiān)視器鎖鎖住的,那么==wait()方法在掛起該線程的同時(shí)釋放該線程的監(jiān)視器鎖==.

? 注意:如果一個(gè)線程同時(shí)擁有A,B兩個(gè)對(duì)象的監(jiān)視器鎖,調(diào)用了對(duì)象A的wait()方法后并不會(huì)釋放B對(duì)象的監(jiān)視器鎖.

? 線程被掛起后是有可能無(wú)緣無(wú)故被喚醒的,這叫做虛假喚醒,通常在喚醒的條件上加while循環(huán)(取非)以避免這種情況.

(2). wait(long timeout)函數(shù)

? 多了一個(gè)參數(shù),這是一個(gè)毫秒(千分之一秒)數(shù),當(dāng)過(guò)了這個(gè)時(shí)間并且還沒(méi)有被notify或者notifyAll()方法喚醒,那么該函數(shù)還是會(huì)因?yàn)槌瑫r(shí)而返回.

(3). notify()函數(shù)

? 隨機(jī)喚醒一個(gè)在該變量的wait()方法上掛起的線程(該對(duì)象可能掛了多個(gè)線程).和wait()方法一樣,都需要會(huì)的該對(duì)象的監(jiān)視器鎖才能調(diào)用.

graph TB
    id0(因?yàn)閣ait方法掛起)
    id1(等待競(jìng)爭(zhēng)監(jiān)視器鎖,所有因?yàn)閣ait方法被掛起的線程被掛起前一定擁有該對(duì)項(xiàng)的監(jiān)視器鎖,在被喚醒后還會(huì)因?yàn)闆](méi)有該鎖而被掛起)
    id2(繼續(xù)執(zhí)行)
    id0--notify方法-->id1
    id1--得到鎖-->id2

(4). notifyAll方法

? 會(huì)喚醒所有因?yàn)樵摴蚕碜兞康膚ait()方法而被掛起的線程.

4. 等待線程執(zhí)行終止的join方法

? 在主線程中使用,該方法會(huì)掛起主線程,當(dāng)子線程執(zhí)行完之后返回該方法.

5. 讓線程睡眠的sleep方法

? Thread類中有一個(gè)靜態(tài)的Sleep方法,當(dāng)一個(gè)執(zhí)行中的線程調(diào)用了Thread的sleep方法,調(diào)用線程會(huì)暫停讓出CPU的執(zhí)行權(quán),但==并不讓出所擁有的監(jiān)視器資源==.知道達(dá)到指定的睡眠時(shí)間后該函數(shù)返回,線程處于就緒狀態(tài).

6. 讓出CPU執(zhí)行權(quán)的yield方法

? Thread類中有一個(gè)靜態(tài)的yield方法,當(dāng)一個(gè)線程運(yùn)行這個(gè)方法時(shí),實(shí)際上就是按時(shí)線程調(diào)度器讓出當(dāng)前自己的CPU使用權(quán),然后立即處于就緒狀態(tài),參與線程輪換.

? 與sleep的不同在于yield方法不會(huì)掛起當(dāng)前線程.

7. 線程中斷

? ==Java中的線程中斷是一種線程間的協(xié)作模式,通過(guò)修改線程對(duì)象中的一個(gè)標(biāo)識(shí)(中斷標(biāo)識(shí)),在線程運(yùn)行代碼中檢測(cè)該標(biāo)識(shí)的值,并作出一些動(dòng)作響應(yīng)中斷.==可以是退出線程也可以是yield,取決于代碼實(shí)現(xiàn).

三個(gè)方法:

  • void interrupt()方法:中斷該線程,將中斷標(biāo)記設(shè)置為true.
  • boolean isInterrupted()方法:檢測(cè)該線程是否被中斷
  • boolean interrupted()方法:靜態(tài)方法,檢測(cè)==當(dāng)前調(diào)用該方法的線程==是否被中斷,如果被中斷,修改中斷標(biāo)記為false.

? 另一種用法:當(dāng)出于一些原因需要立刻喚醒某個(gè)線程(因wait()或sleep()方法被掛起)時(shí),可以調(diào)用這個(gè)線程的interrupt()方法,強(qiáng)制拋出InterruptedException異常,并且返回,線程恢復(fù)到激活狀態(tài).

8. 理解線程上下文切換

? CPU資源的分配采用時(shí)間片輪換的策略,也就是給每一個(gè)線程分配一個(gè)時(shí)間片,線程在時(shí)間片內(nèi)占用CPU執(zhí)行任務(wù).當(dāng)前線程使用完時(shí)間片后就會(huì)處于就緒狀態(tài),讓出CPU供其他線程使用,這就是上下文切換.

? 上下文切換的時(shí)機(jī):當(dāng)前線程的CPU時(shí)間片使用完,處于就緒狀態(tài),當(dāng)前線程被其他線程中斷.

9. 線程死鎖

(1). 什么是線程死鎖

? 死鎖指兩個(gè)或兩個(gè)以上的線程在執(zhí)行過(guò)程中,因爭(zhēng)奪資源而造成的互相等待的現(xiàn)象.

死鎖產(chǎn)生的條件:

  • ==互斥條件==:線程對(duì)已經(jīng)獲取到的資源進(jìn)行排他性使用,即該資源只能由一個(gè)線程占用.(監(jiān)視器鎖只能由一個(gè)線程獲取)
  • ==請(qǐng)求并持有條件==:一個(gè)線程至少持有一個(gè)資源,并要獲取其他新的資源,但這個(gè)資源已經(jīng)被其他資源占有.(持有資源,等待別人占有的資源)
  • ==不可剝奪條件==:線程獲取到的資源在自己使用完之前不能被其他線程搶占.(保持自己的鎖)
  • ==環(huán)路等待條件==:發(fā)生死鎖時(shí),必然存在線程-資源的喚醒等待鏈.(那么兩個(gè)線程就是相互等待)

(2). 如何避免線程死鎖

? 只需要破壞四個(gè)條件之一即可,但是目前只有請(qǐng)求并持有和環(huán)路等待條件是可以被破壞的.

10. 守護(hù)線程與用戶線程

? Java中的線程分為兩類,分為==daemon線程==(守護(hù)線程,例如GC線程)和==user線程==(用戶線程,例如main函數(shù)所在的主線程).

? ==區(qū)分是當(dāng)最后一個(gè)用戶線程結(jié)束時(shí),JVM會(huì)正常退出,不會(huì)考慮守護(hù)線程的運(yùn)行.==言外之意,只要還存在一個(gè)用戶線程,JVM就不會(huì)退出.(實(shí)質(zhì)是:當(dāng)main線程運(yùn)行結(jié)束后,JVM會(huì)自啟動(dòng)一個(gè)叫DestroyJavaVM的線程,這個(gè)線程會(huì)等待所有用戶線程結(jié)束,然后結(jié)束JVM進(jìn)程)

? 要?jiǎng)?chuàng)建守護(hù)線程只需要==設(shè)置線程的daemon參數(shù)為true==即可.

public static void main(String[] args){
    Thread deamonThread = new Thread(new Runnable(){
        public void run(){
            //do something
        }
    });
    
    //設(shè)置為守護(hù)線程
    deamonThread.setDeamon(true);
    deamonThread.start();
}

? 如果希望在主線程結(jié)束后,JVM馬上結(jié)束,可以將創(chuàng)建的線程設(shè)置為守護(hù)線程.

11. ThreadLocal

? ==ThreadLocal意為線程本地變量==,是解決多線程安全問(wèn)題的另一種途徑。使用線程鎖是用時(shí)間換取空間,而ThreadLocal則是使用空間換取時(shí)間效率。

? ==ThreadLocal類可以在每一個(gè)線程中創(chuàng)建一個(gè)同名的變量,但是會(huì)在每一個(gè)線程中存儲(chǔ)一個(gè)副本,在使用該變量時(shí),會(huì)拿出副本進(jìn)行使用。也就是說(shuō),相當(dāng)于在每一個(gè)線程中都存儲(chǔ)了屬于線程自己的變量。訪問(wèn)該變量時(shí)不受其他線程的影響,該變量時(shí)該線程獨(dú)有的。==

(1). ThreadLocal實(shí)現(xiàn)原理

? ==ThreadLocal類中有一個(gè)成員變量ThreadLocals(類型為ThreadLocalMap,實(shí)際存放在Thread線程對(duì)象中,是ThreadLocal對(duì)象的成員變量)==,這個(gè)變量本來(lái)為null,使用時(shí)才會(huì)初始化.

? ==每次使用ThreadLocal對(duì)象時(shí),會(huì)先從當(dāng)前線程的線程對(duì)象中取出ThreadLocalMap對(duì)象,這是一個(gè)map,然后以這個(gè)ThreadLocal對(duì)象為key,去存儲(chǔ)value或者獲取value.==

? 對(duì)于不同的線程中,由于取得的是不同的ThreadLocalMap對(duì)象,所以取值互不干擾.對(duì)于一個(gè)線程內(nèi)的不同ThreadLacal對(duì)象,由于訪問(wèn)map時(shí)的key不同,所以訪問(wèn)的值也不同

1). ThreadLocalMap getMap(Thread t)

? 根據(jù)傳入的線程對(duì)象獲取線程中的ThreadLocalMap對(duì)象

2). T get()

? 先獲取當(dāng)前線程,然后獲取線程中的ThreadLocalMap對(duì)象,以this對(duì)象(ThreadLocal對(duì)象)為key,取出ThreadLocalMap中的value.

3). void set(T value)

? 先獲取當(dāng)前線程,然后獲取ThreadLocalMap對(duì)象,然后以當(dāng)前的ThreadLocal對(duì)象為key,設(shè)置value.

(2). ThreadLocalMap類

? 這是一個(gè)Map,但是與HashMap不同的是,ThreadLocalMap使用的是開(kāi)放探測(cè)法(HashMap鏈地址法).就意味著大齡的變量存儲(chǔ)在ThreadLocalMap對(duì)象中會(huì)使查詢效率急劇下降.

(3). ThreadLocal內(nèi)存泄漏問(wèn)題

? ThreadLocalMap的key使用了弱引用,==當(dāng)key不被引用時(shí),系統(tǒng)回收內(nèi)存會(huì)自動(dòng)清理key的內(nèi)存(清理ThreadLocal對(duì)象)==。導(dǎo)致map中的value無(wú)法被訪問(wèn),但問(wèn)題在于map中的entry以屬性的方式包含了object value。這樣一來(lái)導(dǎo)致沒(méi)有key可以訪問(wèn)這個(gè)value,但是==存在一條強(qiáng)引用鏈:CurrentThread->ThreadLocalMap->Entry->Value==。這條鏈?zhǔn)遣荒鼙籊C(垃圾回收)自動(dòng)回收的。這樣就導(dǎo)致這些value數(shù)據(jù)既不能被訪問(wèn)而手動(dòng)清理,也不會(huì)被被自動(dòng)清理,就形成了內(nèi)存泄露.

? ThreadLocalMap內(nèi)部Entry中key使用的是對(duì)ThreadLocal對(duì)象的弱引用,這為避免內(nèi)存泄露是一個(gè)進(jìn)步,因?yàn)槿绻菑?qiáng)引用,那么即使其他地方?jīng)]有對(duì)ThreadLocal對(duì)象的引用,ThreadLocalMap中的ThreadLocal對(duì)象還是不會(huì)被回收,而如果是弱引用則這時(shí)候ThreadLocal引用是會(huì)被回收掉的,雖然對(duì)于的value還是不能被回收,這時(shí)候ThreadLocalMap里面就會(huì)存在key為null但是value不為null的entry項(xiàng),雖然ThreadLocalMap提供了set,get,remove方法在一些時(shí)機(jī)下會(huì)對(duì)這些Entry項(xiàng)進(jìn)行清理,但是這是不及時(shí)的,也不是每次都會(huì)執(zhí)行的,所以一些情況下還是會(huì)發(fā)生內(nèi)存泄露,所以在使用完畢后即使調(diào)用remove方法才是解決內(nèi)存泄露的王道。

(4). ThreadLocal不支持繼承性

? 父線程的ThreadLocal對(duì)象在子線程中是獲取不到的,因?yàn)檫@是兩個(gè)Thread對(duì)象.

(5). InheritableThreadLocal類

? 這個(gè)類是為了解決ThreadLocal類不支持繼承而創(chuàng)造的,它繼承自ThreadLocal類.提供了一個(gè)特性,可以讓子線程訪問(wèn)父線程中設(shè)置的本地變量.

ThreadLocalMap源碼:

/**
 * Construct a new map including all Inheritable ThreadLocals
 * from given parent map. Called only by createInheritedMap.
 *
 * @param parentMap the map associated with parent thread.
 */
private ThreadLocalMap(ThreadLocalMap parentMap) {
    Entry[] parentTable = parentMap.table;
    deint len = parentTable.length;
    setThreshold(len);
    table = new Entry[len];

    for (int j = 0; j < len; j++) {
        Entry e = parentTable[j];
        if (e != null) {
            @SuppressWarnings("unchecked")
            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            if (key != null) {
                Object value = key.childValue(e.value);
                Entry c = new Entry(key, value);
                int h = key.threadLocalHashCode & (len - 1);
                while (table[h] != null)
                    h = nextIndex(h, len);
                table[h] = c;
                size++;
            }
        }
    }
}

? 從上面的構(gòu)造可以看出,這種構(gòu)造方式可以將父線程的ThreadLocalMap復(fù)制給子線程.達(dá)到共用的目的.

? 經(jīng)測(cè)試,但一旦建立子線程,更改父線程中的InheritableThreadLocal對(duì)象后,子線程并不能察覺(jué).

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        InheritableThreadLocal<Integer> inheritableThreadLocal = new InheritableThreadLocal<>();
        inheritableThreadLocal.set(1);
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("chile thread " + inheritableThreadLocal.get());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("chile thread " + inheritableThreadLocal.get());
            }
        });

        thread.start();
        Thread.sleep(500);
        System.out.println("parents thread " + inheritableThreadLocal.get());
        inheritableThreadLocal.set(2);
        System.out.println("set 2");
        System.out.println("parents thread " + inheritableThreadLocal.get());
        thread.join();
    }
}

// chile thread 1
// parents thread 1
// set 2
// parents thread 2
// chile thread 1
?著作權(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),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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