成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Java多線程筆記(一):JMM與基礎(chǔ)關(guān)鍵字

cyixlq / 1337人閱讀

摘要:當(dāng)線程執(zhí)行完后進(jìn)入狀態(tài),表示線程執(zhí)行結(jié)束。其中和表示兩個(gè)線程。但要注意,讓出并不表示當(dāng)前線程不執(zhí)行了。關(guān)鍵字其作用是防止指令重排和使線程對(duì)一個(gè)對(duì)象的修改令其他線程可見。

JMM特性一覽

Java Memory Model的關(guān)鍵技術(shù)點(diǎn)都是圍繞著多線程的原子性、可見性和有序性來(lái)建立的。因此我們首先需要來(lái)了解這些概念。

原子性(Atomicity)

原子性是指一個(gè)操作是不可中斷的。即使是在多個(gè)線程一起執(zhí)行的時(shí)候,一個(gè)人操作一旦開始,就不會(huì)被其他的線程干擾。

比如對(duì)一個(gè)靜態(tài)全局變量int i,兩個(gè)線程同時(shí)對(duì)它賦值,線程A給他賦值1,線程B給它賦值為-1.那么不管這么2個(gè)線程以合作方式、何種步調(diào)工作,i的值要么是1,要么是-1。線程A和B之間是沒有干擾的。這就是原子性的一個(gè)特點(diǎn),不可被中斷。

可見性(Visibility)

可見性是指當(dāng)一個(gè)線程修改了某一個(gè)共享變量的值,其他線程是否能夠立即知道這個(gè)修改。顯然,對(duì)于串行程序來(lái)說,可見性問題是不存在的。因?yàn)槟阍谌魏我粋€(gè)操作步驟中修改了某個(gè)變量,那么在后續(xù)的步驟中,讀取這個(gè)變量的值,一定是修改后的新值。

有序性(Ordering)

有序性問題是三個(gè)問題中最難理解的。對(duì)于一個(gè)線程的執(zhí)行代碼而言,我們總是習(xí)慣地認(rèn)為代碼的執(zhí)行是從先往后,依次執(zhí)行。這么理解也不是說完全錯(cuò)誤,因?yàn)榫鸵粋€(gè)線程內(nèi)而言,確實(shí)會(huì)表現(xiàn)成這樣。但是,在并發(fā)時(shí),程序的執(zhí)行可能就會(huì)出現(xiàn)亂序。給人直觀的感覺就是:寫在前面的代碼,會(huì)在后面執(zhí)行。然而有序性的問題的原因因?yàn)槭?strong>程序在執(zhí)行時(shí),可能會(huì)進(jìn)行指令重排,重排后的指令與原指令的順序未必一致。 那么在這里由于篇幅關(guān)系就不在展開介紹,有興趣的讀者可以自行搜索Java指令重排CPU流水線等資料。

哪些指令不能重排——Happen-Before規(guī)則

雖然Java虛擬機(jī)和執(zhí)行系統(tǒng)會(huì)對(duì)指令進(jìn)行一定的重排,但是指令重排是有規(guī)則的,并非所有的指令都可以隨便改變位置。原則基本包括以下:

程序順序原則:一個(gè)線程內(nèi)保證語(yǔ)義的串行性

  a=1;
  b=a+1;
  //第二條語(yǔ)句依賴于第一條執(zhí)行結(jié)果。所以不允許指令重排。

volatile規(guī)則:volatile變量的寫,先發(fā)生與讀,這保證了volatile變量的可見性。一般用volatile修飾的都是經(jīng)常修改的對(duì)象。

鎖規(guī)則:解鎖(unlock)必然發(fā)生在隨后的加鎖(lock)前

傳遞性:A先于B,B先于C,那么A必然先于C

線程的start()方法先于它的每一個(gè)動(dòng)作

線程的所有操作先于線程的終結(jié)(Thread.join())

線程的中斷(interrupt())先于被中斷線程的代碼

對(duì)象的構(gòu)造函數(shù)執(zhí)行、結(jié)束先于finalize()方法

Java多線程

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

public enum State {
    /**
    * 表示剛剛創(chuàng)建的線程,這種線程還沒開始執(zhí)行。
    **/
    NEW,
    /**
    * 調(diào)用start()方法后,線程開始執(zhí)行,處于RUNNABLE狀態(tài),
    * 表示線程所需要的一切資源以及準(zhǔn)備好。
    **/
    RUNNABLE,
    /**
    * 當(dāng)線程遇到synchronized同步塊,就進(jìn)入了BLOCKED阻塞狀態(tài)。
    * 這時(shí)線程會(huì)暫停執(zhí)行,直到獲得請(qǐng)求的鎖。
    **/
    BLOCKED,
    /**
    * WAITING和TIMED_WAITING都表示等待狀態(tài),他們是區(qū)別是WAITING表示進(jìn)入一個(gè)無(wú)時(shí)間限制的等待
    * TIMED_WAITING會(huì)進(jìn)入一個(gè)有時(shí)間限制的等待。
    * WAITING的狀態(tài)正是在等待特殊的事件,如notify()方法。而通過join()方法等待的線程,則是等待目標(biāo)線程的終止。
    * 一旦等到期望的時(shí)間,線程就會(huì)繼續(xù)執(zhí)行,進(jìn)入RUNNABLE狀態(tài)。
    * 當(dāng)線程執(zhí)行完后進(jìn)入TERMINATED狀態(tài),表示線程執(zhí)行結(jié)束。
    **/
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}
線程的基本操作 新建線程

新建線程很簡(jiǎn)單。只要使用new關(guān)鍵字創(chuàng)建一個(gè)線程對(duì)象,并且將其start()起來(lái)即可。start()方法額就會(huì)新建一個(gè)線程并讓這個(gè)線程執(zhí)行run()方法。

常見就是有人直接對(duì)一個(gè)線程對(duì)象執(zhí)行run()方法,那么只會(huì)在當(dāng)前的線程中串行執(zhí)行run()中的代碼 。

最后要說的是,默認(rèn)的Thread.run()就是直接調(diào)用內(nèi)部的Runnable接口。因此,使用Runnable接口告訴線程該做什么,更為合理。

終止線程

Stop()方法是用不得的,會(huì)直接終止運(yùn)行中的線程,并立刻釋放鎖。比如一個(gè)線程寫數(shù)據(jù)到一般被中止,則會(huì)寫壞。

那么最簡(jiǎn)單的方法可以考慮給線程做一個(gè)死循環(huán),然后對(duì)一個(gè)類似Flag的變量進(jìn)行判斷,變量變化時(shí)退出循環(huán)。JDK所提供的線程中斷也是類似于此。

線程中斷

線程中斷是重要的線程協(xié)作機(jī)制,中斷就是讓線程停止執(zhí)行,但這個(gè)停止執(zhí)行非stop()的暴力方式。JDK提供了更安全的支持,就是線程中斷。
線程中斷并不會(huì)使線程立即停止,而是給線程發(fā)送一個(gè)通知,告訴目標(biāo)線程有人希望你退出。至于目標(biāo)線程接到通知后什么時(shí)候停止,完全由目標(biāo)線程自行決定。這點(diǎn)很重要,如果線程接到通知后立即退出,我們就又會(huì)遇到類似stop()方法的老問題。
與線程有關(guān)的三個(gè)方法,

中斷線程

void Thread.interrupt()

說明:Thread.interrupt() 是一個(gè)實(shí)例方法,他通知目標(biāo)線程中斷,也就是設(shè)置中斷標(biāo)志位。中斷標(biāo)志位表示當(dāng)前線程已經(jīng)被中斷了。

判斷是否被中斷

boolean Thread.isInterrupted()

說明:Thread.isInterrupted() 也是實(shí)例方法,他判斷當(dāng)前線程是否被中斷(通過檢查中斷標(biāo)志位)

判斷是否被中斷,并清除當(dāng)前中斷狀態(tài)

static boolean Thread.interrupted()

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

Thread.sleep()方法會(huì)讓當(dāng)前線程休眠若干時(shí)間,它會(huì)拋出一個(gè)interruptedException中斷異常。interruptedException是必須被捕獲的——當(dāng)線程在sleep時(shí),如果被中斷,這個(gè)異常就產(chǎn)生。

public class InterruptExample {

    public static void main(String [] a) throws InterruptedException{

        Thread t1 = new Thread("線程小哥 - 1 "){
            @Override
            public void run() {
                while (true){
                    /**
                     * 必須得判斷是否接受到中斷通知,如果不寫退出方法,也無(wú)法將當(dāng)前線程退出.
                     */
                    if (Thread.currentThread().isInterrupted()){
                        System.out.println(Thread.currentThread().getName() + " Interrupted ... ");
                        break;
                    }

                    try {
                        /**
                         * 處理業(yè)務(wù)邏輯花費(fèi)10秒.
                         * 而在這時(shí),主線程發(fā)送了中斷通知,當(dāng)線程在sleep的時(shí)候如果收到中斷
                         * 則會(huì)拋出InterruptedException,如果在異常中不處理,則線程不會(huì)中斷.
                         *
                         */
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        System.out.println("線程在睡眠中遭到中斷....");
                        /**
                         * 在sleep過程中,收到中斷通知,拋出異常.可以直接退出線程.
                         * 但如果還需要處理其他業(yè)務(wù),則需要重新中斷自己.設(shè)置中斷標(biāo)記位.
                         * 這樣在下次循環(huán)的時(shí)候 線程發(fā)現(xiàn)中斷通知,才能正確的退出.
                         */
                        Thread.currentThread().interrupt();
                    }

                    Thread.yield();
                }
            }
        };

        t1.start();
        try {
            /**
             * 處理業(yè)務(wù)500毫秒
             * 然后發(fā)送中斷通知,此時(shí)t1線程還在sleep中.
             */
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        /**
         * 給目標(biāo)線程發(fā)送中斷通知
         * 目標(biāo)線程中必須有處理中斷通知的代碼
         * 否則,就算發(fā)送了通知,目標(biāo)線程也無(wú)法停止.
         */
        t1.interrupt();
    }
}
等待(wait)和通知(notify)

為了支持多線程之間的協(xié)作,JDK提供了兩個(gè)非常重要的等待方法wait()和nofity()方法。這兩個(gè)方法并不是Thread類中的,而是Object類,這意味著任何對(duì)象都可以調(diào)用這兩個(gè)方法。

這兩個(gè)方法的簽名如下:

public final void wait() throws InterruptedException
public final native void notify()

如果一個(gè)線程調(diào)用了object.wait()方法,那么這個(gè)線程就會(huì)停止執(zhí)行而轉(zhuǎn)為等待狀態(tài),進(jìn)入obj對(duì)象的等待隊(duì)列。這個(gè)等待隊(duì)列可能有多個(gè)線程,因?yàn)橄到y(tǒng)運(yùn)行多個(gè)線程同時(shí)等待同一個(gè)對(duì)象。其他線程調(diào)用obj.notify()方法時(shí),它就會(huì)從等待隊(duì)列中隨機(jī)選擇一個(gè)線程并將其喚醒。注意這個(gè)選擇是不公平的,是隨機(jī)的。

object.wait()方法并不是可以隨便調(diào)用。它必須包含在對(duì)應(yīng)的synchronized語(yǔ)句中。無(wú)論是wait還是notify都必須首先獲得目標(biāo)對(duì)象的一個(gè)監(jiān)視器 。如下圖,顯示了wait()和nofity的工作流程細(xì)節(jié)。其中T1和T2表示兩個(gè)線程。T1在正確執(zhí)行wait方法后,首先必須獲得object對(duì)象的監(jiān)視器。而wait方法在執(zhí)行后,會(huì)釋放這個(gè)監(jiān)視器,這樣做的目的使得其他等待object對(duì)象上的線程不至于因?yàn)門1的休眠而全部無(wú)法正常執(zhí)行。

線程T2在notify()調(diào)用前,也必須獲得object的監(jiān)聽器。所幸,此時(shí)T1已經(jīng)釋放了這個(gè)監(jiān)視器。因此,T2可以順利獲得object的監(jiān)視器。接著,T2執(zhí)行了notify()方法嘗試喚醒一個(gè)等待線程,這里假設(shè)喚醒了T1。T1在被喚醒后,要做的第一件事并不是執(zhí)行后續(xù)的代碼,而是要嘗試重新獲得object的監(jiān)視器。而這個(gè)監(jiān)視器也正是T1在wait()方法執(zhí)行前所持有的那個(gè)。如果暫時(shí)無(wú)法獲得,T1還必須要等待這個(gè)監(jiān)視器。當(dāng)監(jiān)視器順利獲得后,T1才可以真正意義上的繼續(xù)執(zhí)行。

注意::Object.wait()和Thread.sleep()方法都可以讓線程等待若干的時(shí)間。除了wait()可以被喚醒外,另一個(gè)最主要的區(qū)別就是wait()方法會(huì)釋放目標(biāo)對(duì)象的鎖,而Thread.sleep()方法不會(huì)釋放任何資源。

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

不推薦使用suspend()去掛起線程 的原因是因?yàn)閟uspend()在導(dǎo)致線程暫停的同時(shí),并不會(huì)去釋放任何資源。此時(shí),其他任何線程都想訪問它暫用的鎖時(shí),都會(huì)被導(dǎo)致牽連,導(dǎo)致無(wú)法正常運(yùn)行。直到對(duì)應(yīng)的線程上進(jìn)行了resume()操作,被掛起的線程才能繼續(xù),從而其他所有阻塞在相關(guān)鎖上的線程也可以繼續(xù)執(zhí)行。但是,如果resume()操作意外地在suspend()前就執(zhí)行了,那么被掛起的線程可能就很難有機(jī)會(huì)被繼續(xù)執(zhí)行。并且,更嚴(yán)重的是:它鎖占用的鎖不會(huì)被釋放,因此可能會(huì)導(dǎo)致整個(gè)操作系統(tǒng)工作不正常。而且,對(duì)于被掛起的線程,從它的線程上看狀態(tài),居然會(huì)是Runnable,這是最氣的。

等待線程結(jié)束(join)和謙讓(yield)

join的方法簽名:

public final void join () throws InterruptedException //一直阻塞當(dāng)前線程,直到目標(biāo)線程執(zhí)行完畢
public final synchronized void join (long millis) throws InterruptedException//和之前一樣,不過增加了最大等待時(shí)間
public static native void yield();

這是一個(gè)靜態(tài)方法,一旦執(zhí)行,它會(huì)使當(dāng)前線程讓出CPU。但要注意,讓出CPU并不表示當(dāng)前線程不執(zhí)行了。當(dāng)前線程在讓出CPU以后,還會(huì)進(jìn)行CPU資源爭(zhēng)奪,但是是否能夠再次分配到就要看人品了。

如果你覺得一個(gè)線程不那么重要,或者優(yōu)先級(jí)非常低,而且又害怕它會(huì)占用太多的CPU資源,那么可以在適當(dāng)?shù)臅r(shí)候調(diào)用Thread.yield(),給予其他重要線程更多的工作機(jī)會(huì)。

關(guān)鍵字volatile

其作用是防止CPU指令重排和使線程對(duì)一個(gè)對(duì)象的修改令其他線程可見。

對(duì)于Java的內(nèi)存模型來(lái)說,每個(gè)volatile會(huì)在線程的工作內(nèi)存從保留一個(gè)拷貝,只不過java內(nèi)存模型通過對(duì)volatile變量的添加了特殊機(jī)制保證了變量的可見性。線程在修改volatile類型變量以后必須立即保存到主內(nèi)存,在使用變量前必須從主內(nèi)存加載數(shù)據(jù),同時(shí)還做了一些禁止指令重排序的操作。對(duì)于各個(gè)線程的工作內(nèi)存(私有內(nèi)存)來(lái)說,存在volatile變量不一致的時(shí)刻,但是對(duì)于執(zhí)行引擎來(lái)說,通過了上面的幾條規(guī)則保證了變量是一致的。

可參考: Java并發(fā)編程之volatile關(guān)鍵字解析

線程安全的概念與synchronized

并行程序開發(fā)的一大關(guān)注重點(diǎn)就是線程安全。一般來(lái)說,程序并行化就是為了獲得更高的執(zhí)行效率,但前提是,不能以犧牲正確性為代價(jià)。如果程序并行化以后,連基本的執(zhí)行結(jié)果都無(wú)法保證,那么并行程序本身也就沒有任何意義了。

volatile并不能真正的保障線程安全。它只能確保一個(gè)線程修改了數(shù)據(jù)后,其他線程能夠看到這個(gè)改動(dòng)。但當(dāng)兩個(gè)線程同時(shí)修改某一個(gè)數(shù)據(jù)時(shí),卻依然會(huì)產(chǎn)生沖突。

關(guān)鍵字synchronized的作用是實(shí)現(xiàn)線程間的同步。它的工作是對(duì)同步的代碼加鎖,使得每一次,只能有一個(gè)線程進(jìn)入同步塊,從而保證線程間的安全。

關(guān)鍵字synchronized可以有多種用法:

指定加鎖對(duì)象:對(duì)給定對(duì)象加鎖,進(jìn)入同步代碼前要獲得給定對(duì)象的鎖。

直接作用于實(shí)例方法:相當(dāng)于對(duì)當(dāng)前實(shí)例加鎖,進(jìn)入同步代碼前要獲得當(dāng)前實(shí)例的鎖。

直接作用于靜態(tài)方法:相當(dāng)于對(duì)當(dāng)前類加鎖,進(jìn)入同步代碼前要獲得當(dāng)前類的鎖。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/65184.html

相關(guān)文章

  • 《深入理解 Java 內(nèi)存模型》讀書筆記

    摘要:前提深入理解內(nèi)存模型程曉明著,該書在以前看過一遍,現(xiàn)在學(xué)的東西越多,感覺那塊越重要,于是又再細(xì)看一遍,于是便有了下面的讀書筆記總結(jié)。同步同步是指程序用于控制不同線程之間操作發(fā)生相對(duì)順序的機(jī)制。線程之間的通信由內(nèi)存模型控制。 showImg(https://mmbiz.qpic.cn/mmbiz_jpg/1flHOHZw6RtPu3BNx3zps1JhSmPICRw7QgeOmxOfTb...

    姘存按 評(píng)論0 收藏0
  • 《深入理解 Java 內(nèi)存模型》讀書筆記

    摘要:前提深入理解內(nèi)存模型程曉明著,該書在以前看過一遍,現(xiàn)在學(xué)的東西越多,感覺那塊越重要,于是又再細(xì)看一遍,于是便有了下面的讀書筆記總結(jié)。同步同步是指程序用于控制不同線程之間操作發(fā)生相對(duì)順序的機(jī)制。線程之間的通信由內(nèi)存模型控制。 showImg(https://segmentfault.com/img/remote/1460000013474312?w=1920&h=1271); 前提 《深...

    xuexiangjys 評(píng)論0 收藏0
  • 深入理解Java內(nèi)存模型()——基礎(chǔ)

    摘要:線程之間的通信由內(nèi)存模型本文簡(jiǎn)稱為控制,決定一個(gè)線程對(duì)共享變量的寫入何時(shí)對(duì)另一個(gè)線程可見。為了保證內(nèi)存可見性,編譯器在生成指令序列的適當(dāng)位置會(huì)插入內(nèi)存屏障指令來(lái)禁止特定類型的處理器重排序。 并發(fā)編程模型的分類 在并發(fā)編程中,我們需要處理兩個(gè)關(guān)鍵問題:線程之間如何通信及線程之間如何同步(這里的線程是指并發(fā)執(zhí)行的活動(dòng)實(shí)體)。通信是指線程之間以何種機(jī)制來(lái)交換信息。在命令式編程中,線程之間的...

    jsdt 評(píng)論0 收藏0
  • 深入理解Java內(nèi)存模型(七)——總結(jié)

    摘要:編譯器,和處理器會(huì)共同確保單線程程序的執(zhí)行結(jié)果與該程序在順序一致性模型中的執(zhí)行結(jié)果相同。正確同步的多線程程序的執(zhí)行將具有順序一致性程序的執(zhí)行結(jié)果與該程序在順序一致性內(nèi)存模型中的執(zhí)行結(jié)果相同。 前情提要 深入理解Java內(nèi)存模型(六)——final 處理器內(nèi)存模型 順序一致性內(nèi)存模型是一個(gè)理論參考模型,JMM和處理器內(nèi)存模型在設(shè)計(jì)時(shí)通常會(huì)把順序一致性內(nèi)存模型作為參照。JMM和處理器內(nèi)...

    paney129 評(píng)論0 收藏0
  • 后臺(tái)開發(fā)常問面試題集錦(問題搬運(yùn)工,附鏈接)

    摘要:基礎(chǔ)問題的的性能及原理之區(qū)別詳解備忘筆記深入理解流水線抽象關(guān)鍵字修飾符知識(shí)點(diǎn)總結(jié)必看篇中的關(guān)鍵字解析回調(diào)機(jī)制解讀抽象類與三大特征時(shí)間和時(shí)間戳的相互轉(zhuǎn)換為什么要使用內(nèi)部類對(duì)象鎖和類鎖的區(qū)別,,優(yōu)缺點(diǎn)及比較提高篇八詳解內(nèi)部類單例模式和 Java基礎(chǔ)問題 String的+的性能及原理 java之yield(),sleep(),wait()區(qū)別詳解-備忘筆記 深入理解Java Stream流水...

    spacewander 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<