Java線程學(xué)習(xí)筆記

一. 概述

使用的線程的目的有如下幾點:

  1. 異步锨匆。所謂異步崭别,字義上來講就是同時做多個不同的事。
    例如恐锣,你正在和戀人聊QQ茅主,而此時你正在發(fā)送一個文件,如果收發(fā)消息和上傳文件在同一個線程土榴,那么當(dāng)你發(fā)送文件開始诀姚,你便需要等待文件上傳完成后,才能發(fā)送下一條消息玷禽,如果再加上文件太大赫段、網(wǎng)速很差等等因素,就可能導(dǎo)致你和你戀人在幾個小時內(nèi)只說了一句話矢赁,那你們之間可能就GG了糯笙。
  1. 并發(fā)。所謂并發(fā)撩银,字義上來講就是同時做多個相同的事给涕。
    例如,雙11上淘寶買東西额获,在同一時間內(nèi)够庙,阿里的服務(wù)器收到一多個購買的請求,如果處理這些請求的方式是處理完一條再處理下一條的話抄邀,那么你買個衣服坑你就要等到地老天荒了耘眨。

雖然我在這里將線程的目的分成了兩種,但它們的本質(zhì)是一樣:在同一時間做多個不同的事境肾。值得注意的是剔难,如果你電腦的CPU是單核單線程的話胆屿,這個“同一時間”是有事件差的,可能CPU這一毫秒在執(zhí)行線程A钥飞,下一毫秒在執(zhí)行線程B,但完全同一時間同時執(zhí)行線程A和線程B是不可能的衫嵌。

二. 如何創(chuàng)建線程

實現(xiàn)線程有兩種方式读宙,涉及到的類有兩個,分別是:java.lang.Thread和java.lang.Runnable楔绞。創(chuàng)建并啟動線程的方式如下:

Thread thread = new Thread();//創(chuàng)建線程實例
thread.start();//啟動線程

但是這僅僅是創(chuàng)建了一條新的線程并啟動了它结闸,該線程并不會執(zhí)行任何邏輯,那么我們怎么讓它執(zhí)行相關(guān)邏輯呢酒朵?

方式1:繼承Thread類桦锄,重寫run方法。

public class DemoThread extends Thread {
    public void run() {
        //TODO 線程中需要執(zhí)行的相關(guān)邏輯
    }

    public static void main(String[] args) {
        Thread thread = new DemoThread();//創(chuàng)建線程實例
        thread.start();//啟動線程
    }
}

方式二:實現(xiàn)Runnable接口呆万,并在Thread構(gòu)造方法中傳入實現(xiàn)的Runnable實例埋泵。

public class DemoRunnable implements Runnable {
    public void run() {
        //TODO 線程中需要執(zhí)行的相關(guān)邏輯
    }

    public static void main(String[] args) {
        Runnable runnable = new DemoRunnable();//創(chuàng)建一個實現(xiàn)了Runnable接口的類的實例
        Thread thread = new Thread(runnable);//創(chuàng)建線程實例润绎,并在構(gòu)造方法中傳入實現(xiàn)了Runnable類的實例
        thread.start();//啟動線程
    }
}

注: 這兩種方法都能讓線程執(zhí)行我們需要執(zhí)行的邏輯代碼。當(dāng)你調(diào)用start()函數(shù)啟動線程后图甜,程序會在該線程中調(diào)用Thread類自己的run()方法,如果你在創(chuàng)建線程時傳入了Runnable的實例鳖眼,那么在Thread類的run()方法中黑毅,會調(diào)用Runnable的run()方法。
特別需要注意的是钦讳,new Thread()只是創(chuàng)建了Thread類的一個實例矿瘦,此時并沒有創(chuàng)建出一條線程。而啟動線程是調(diào)用start()方法而不是run()方法:直接調(diào)用run()方法時你只是調(diào)用的一個普通方法去執(zhí)行相關(guān)邏輯代碼愿卒,邏輯依然執(zhí)行再你調(diào)用run()方法的線程中缚去;而調(diào)用start()方法,jvm才會創(chuàng)建一條新線程琼开,而此時run()才會在該線程中自動被回調(diào)執(zhí)行病游。

三、線程同步

什么是同步稠通?

同步是某個任務(wù)在同一時間只能由一個線程在執(zhí)行衬衬,等這個線程執(zhí)行完成后,下一個線程才能執(zhí)行改橘。那么既然線程的目的是為了異步滋尉,那么又為什么需要同步呢?這主要是由于數(shù)據(jù)安全造成的飞主。多個線程在同時操作一個數(shù)據(jù)狮惜,當(dāng)線程A還沒沒來得急使用該數(shù)據(jù)時高诺,線程B就改了該數(shù)據(jù)的狀態(tài)或值,這是就可能導(dǎo)致結(jié)果的錯誤碾篡,甚至是程序運行的異常虱而。就像由此你和朋友都很餓,然后看到一個蘋果开泽,你正準(zhǔn)備吃牡拇,然后你朋友直接給你搶來吃的還剩核,然后......例子可能不精確穆律,見諒惠呼。

實現(xiàn)線程同步主要要使用兩個點:鎖和synchronized關(guān)鍵字。

1.鎖峦耘。
鎖是java中一種機制剔蹋,分為對象鎖和類鎖。每個對象/類都有一個單一的鎖(需要注意的是辅髓,一個類可以有多個對象泣崩,每個對象的鎖也都是獨立且單一的,互不干擾)洛口。當(dāng)一個線程獲取到某個對象/類的鎖后律想,除非該鎖被釋放,否則其他線程是不能獲取到該對象/類的鎖绍弟,而此時如果其他線程要獲取該對象的鎖技即,就只能等待。而上面所說的某個任務(wù)樟遣,便是獲取到同一個鎖的代碼塊而叼,他可能里面的邏輯并不相同,但是獲取的鎖是相同的豹悬。

2葵陵、synchronized關(guān)鍵字用于需要同步的邏輯中,實現(xiàn)方式分為:同步方法和同步塊瞻佛。synchronized的目的就是獲取某個對象/類的鎖脱篙。分為:

同步塊。其中的object參數(shù)便是你要獲取的鎖的對象伤柄。

synchronized (object) {
    //TODO 這里是同步塊中需要執(zhí)行的邏輯
}

對象同步方法绊困。該synchronized獲取到的鎖是類A時候化后的對象的鎖。

public class A {
    public synchronized void syncMethod() {
        //TODO 這里是同步方法中需要執(zhí)行的邏輯
    }
}

類同步方法适刀,即靜態(tài)同步方法秤朗。該synchronized獲取到的鎖是類A的鎖。

public class A {
    public static synchronized void staicSyncMethod() {

    }
}

同步的例子

public class ThreadSyncDemo {

    private static Object locker = new Object();//需要獲取鎖的對象

    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (locker) {
                    for (int i = 0; i < 1000; ++i) {
                        System.out.println(i);
                    }
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (locker) {
                    for (int i = 0; i < 1000; ++i) {
                        System.out.println("aaa");
                    }
                }
            }
        });

        t1.start();
        t2.start();
    }
}

執(zhí)行這段代碼時笔喉,你先將synchronized塊去掉取视,保留里面的邏輯硝皂,然后執(zhí)行,此時你會返現(xiàn)控制臺輸出中作谭,數(shù)字和字母是交叉出現(xiàn)的稽物,說明是異步執(zhí)行的。然后加上同步快折欠,再執(zhí)行贝或,你會發(fā)現(xiàn)此時數(shù)字先打印完后才打印的字母(或者字母先打印完后才打印的字母),說明是同步執(zhí)行的怨酝。對象同步方法和靜態(tài)同步方法原理的列子這里就不再舉出傀缩,只要知道到底是獲取到那個對象或類的鎖那先,其他都是類似的农猬。

死鎖

死鎖,顧名思義就是鎖死了售淡,執(zhí)行不下去了斤葱。若有兩個線程A和B,若線程A在執(zhí)行時獲取到對象X的鎖揖闸,在同步塊中又獲取對象Y的鎖揍堕,而此時線程B在執(zhí)行時獲取到對象Y的鎖,而B的同步塊中又在獲取X的鎖汤纸。再某種比較的極端情況下衩茸,A持有X的鎖,B持有Y的鎖贮泞,A執(zhí)行到獲取Y的鎖時B未執(zhí)行完楞慈,A阻塞等待,然后B又獲取X的鎖啃擦,而此時A還在阻塞等待B持有的Y的鎖囊蓝,未釋放X的鎖,導(dǎo)致B也阻塞等待令蛉。A和B都在阻塞等待聚霜,然后就沒有然后了~~~~
所以避免死鎖的其中之一便是盡量不要交叉獲取鎖。當(dāng)然這不是唯一導(dǎo)致死鎖的可能珠叔。
下面是一個死鎖的列子:

public class ThreadSyncDemo {

    private static Object locker = new Object();

    public static void main(String[] args) {
        final Thread t1 = new Thread() {
            @Override
            @Deprecated
            public void run() {
                synchronized (locker) {//獲取ThreadSyncDemo的類鎖
                    for (int i = 0; i < 10000; ++i) {
                        System.out.println(i);
                        if (i == 1000) {
                            this.suspend();//掛起該線程蝎宇,此方法暫停線程執(zhí)行,但不會釋放鎖
                        }
                    }
                }
            }
        };

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (locker) {//獲取ThreadSyncDemo的類鎖
                    for (int i = 0; i < 10000; ++i) {
                        System.out.println("aaa");
                    }
                }
            }
        });

        t1.start();
        try{
            Thread.sleep(50)//暫停下祷安,保證他t1先執(zhí)行夫啊。
        }catch(Exception e){}
        t2.start();
    }
}

上面例子中,線程t1和線程t2的同步塊都是獲取的ThreadSyncDemo.class的鎖辆憔,在t1同步塊中撇眯,當(dāng)i=1000時报嵌,暫停了t1線程的執(zhí)行,此時t1的同步塊未釋放鎖熊榛,而t2一直在等待t1釋放鎖锚国,如果t1線程不繼續(xù)執(zhí)行,則t2也執(zhí)行不了玄坦。

四. Thread類的相關(guān)方法

wait()與notify()/notifyAll()方法

實際上血筑,wait()、notify()煎楣、notifyAll()這三個方法并不是Thread類的專有方法豺总,而是Object的方法,也就是說择懂,每個對象都存在這三個方法喻喳。

需要注意的是,這三個方法都只能在同步塊/同步方法中執(zhí)行困曙,其他地方執(zhí)行時沒有意義的表伦。而且需要同步塊中獲取到的鎖的對象來調(diào)用才有效果。

wait()顧名思義讓同步塊暫停執(zhí)行并等待慷丽,此時該同步塊會讓出獲取到的鎖蹦哼,讓其他線程執(zhí)行獲取同一把鎖的同步塊。而在其他線程執(zhí)行完后要糊,調(diào)用notify()/notifyAll()方法纲熏,之前等待的的同步塊就會繼續(xù)執(zhí)行。但是锄俄,如果調(diào)用了notify()/notifyAll()之后局劲,后面有長時間任務(wù)二導(dǎo)致鎖未被釋放,等待中的同步塊也需要等鎖被釋放后才會繼續(xù)往下執(zhí)行珊膜。如果同一個鎖有多個地方等待容握,就需要使用notifyAll()來全部喚醒,使他們重新爭奪鎖的行列中车柠,誰先獲取到鎖就誰先執(zhí)行剔氏。注意如果等待的是多個,nofity()和nofityAll()后最先獲取的鎖的是那個竹祷,由jvm決定谈跛。
wait()方法有兩個個重載方法,wait(long timeout)和wait(long timeout, int nanos),其中timeout是等待時間塑陵,如果timeout=0感憾,則表示一直等待知道nofity()/nofityAll()被調(diào)用切獲取到鎖后繼續(xù)執(zhí)行,timeout>0則表示等待多少時間后令花,只要獲取到鎖就繼續(xù)執(zhí)行阻桅。至于nanos凉倚,表示納秒,值在0-999999之間嫂沉,為了更好的控制時間稽寒。

public class ThreadSyncDemo {

    private static Object locker_1 = new Object();

    public static void main(String[] args) {
        final Thread t1 = new Thread() {
            @Override
            @Deprecated
            public void run() {
                synchronized (locker_1) {
                    for (int i = 0; i < 1000; ++i) {
                        System.out.println(i);
                        if (i == 100) {
                            try {
                                locker_1.wait();//當(dāng)i=100是,執(zhí)行wait()方法趟章,釋放鎖杏糙,進入等待狀態(tài)
                            } catch (Exception e) {
                            }
                        }
                    }
                }
            }
        };

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (locker_1) {
                    for (int i = 0; i < 100; ++i) {
                        System.out.println("aaa");
                    }
                    locker_1.notify();//當(dāng)執(zhí)行完后,通知等待的線程繼續(xù)執(zhí)行蚓土。
                }
            }
        });

        t1.start();
        try{
            Thread.sleep(50) //保證t1先執(zhí)行
        }catch(Exception e)
        t2.start();
    }
}

上例宏侍,控制臺先會打印0-100,當(dāng)線程t1中i=100時蜀漆,調(diào)用locker_1.wait()使其釋放鎖并進入等待狀態(tài)谅河,此時線程t2獲取到鎖,控制臺打印100個aaa,然后調(diào)用locker_1.notify()后嗜愈,線程t1會繼續(xù)在控制臺打印101-999旧蛾。

interrupt(),interrupted(),isInterrupt()方法

interrupt()方法看起來是中斷線程莽龟,但實際上蠕嫁,當(dāng)你調(diào)用interrupt()方法后,你發(fā)現(xiàn)線程該干嘛還是再干嘛毯盈,除非你的線程中存再調(diào)用sleep()剃毒、wait()等方法的時候,此時會拋出InterruptedException異常搂赋,以供手動處理線程停止赘阀。isInterrupt()返回線程是否是中斷狀態(tài)。interrupted()方法是類方法脑奠。從Thread類的源碼看基公,isInterrupt()和interrupted()都會調(diào)用以下方法:
private native boolean isInterrupted(boolean ClearInterrupted);
不同是isInterrupt()傳入的ClearInterrupted=false,
interrupted()方法傳入的ClearInterrupted=true,當(dāng)ClearInterrupted=true時宋欺,線程的中斷狀態(tài)會被清除轰豆,也就是說此時isInterrupt()方法的返回值是false。

suspend()與resume()方法

已廢棄的方法齿诞,用于暫停和重新開始執(zhí)行線程酸休。和wait()不同的是,suspend()是通過線程的實例調(diào)用的祷杈,而不是鎖對象斑司,調(diào)用也不需要在同步塊中調(diào)用,而且suspend()方法調(diào)用后并不會釋放鎖但汞。列子:

public class ThreaSuspendResumeDemo {

    public static void main(String[] args) {
        final Timer timer = new Timer();

        final Thread t = new Thread() {
            @Deprecated
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    System.out.println(i);
                    if (i == 1000) {
                        this.suspend();//暫停線程
                    }
                }
                timer.cancel();
            }
        };
        t.start();

        timer.schedule(new TimerTask() {
            @Override
            @Deprecated
            public void run() {
                t.resume();//3秒后將線程喚醒
            }
        }, 3000);
    }
}

stop()方法

廢棄的方法宿刮。暴力終止線程互站,調(diào)用此方法后,線程中未執(zhí)行的語句將不會再執(zhí)行僵缺。但是isAlive()方法和isInterrupt方法的返回值依然是false云茸,所以,暴力如此谤饭,想想都可怕标捺,謹(jǐn)慎使用。


public class ThreadStopDemo {

    public static void main(String[] args) {

        final Thread t = new Thread() {
            @Deprecated
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    System.out.println(i);
                    if (i == 1000) {
                        this.stop();//i=1000時就停止了揉抵,控制臺只會打印0-1000亡容。
                    }
                }
                System.out.println("Is this thread alive?" + this.isAlive());//線程已經(jīng)終止,這條語句是不會執(zhí)行的
            }
        };
        t.start();

        final Timer timer = new Timer();
        timer.schedule(new TimerTask(){
            @Override
            @Deprecated
            public void run() {
                System.out.println("Is this thread alive?" + t.isAlive());//false
                System.out.println("Is this thread interrupt?" + t.interrupted());//false
            }
        }, 3000);
    }
}

destroy()方法

額冤今,為什么要講這個方法呢闺兢?因為我以為會和stop()方法一樣的喪心病狂,但是我錯了戏罢。從Thread.destroy()方法的源碼來看屋谭,結(jié)果讓人發(fā)呆流鼻涕。源碼如下:

    /**
     * Throws {@link NoSuchMethodError}.
     *
     * @deprecated This method was originally designed to destroy this
     *     thread without any cleanup. Any monitors it held would have
     *     remained locked. However, the method was never implemented.
     *     If if were to be implemented, it would be deadlock-prone in
     *     much the manner of {@link #suspend}. If the target thread held
     *     a lock protecting a critical system resource when it was
     *     destroyed, no thread could ever access this resource again.
     *     If another thread ever attempted to lock this resource, deadlock
     *     would result. Such deadlocks typically manifest themselves as
     *     "frozen" processes. For more information, see
     *     <a href="{@docRoot}/../technotes/guides/concurrency/threadPrimitiveDeprecation.html">
     *     Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?</a>.
     * @throws NoSuchMethodError always
     */
    @Deprecated
    public void destroy() {
        throw new NoSuchMethodError();
    }

是不是亮瞎了鈦合金狗眼!!!!!

五.守護線程

線程分為用戶(User)線程和守護(Daemon)線程龟糕,守護(Daemon)線程的實現(xiàn)就是線程在調(diào)用start()方法前桐磁,先調(diào)用setDaemon(true)方法。區(qū)別是讲岁,普通用戶(User)線程我擂,只要線程還在執(zhí)行,那么程序就永遠(yuǎn)不會退出缓艳;而守護(Daemon)線程只要程序主線程執(zhí)行完后校摩,守護(Daemon)線程也就被終止。守護(Daemon)線程常作為輔佐的的作用阶淘。

以上便是此次線程學(xué)習(xí)的第一部分總結(jié)衙吩,如有意見或建議歡迎提出,相互探討才能共同成長與進步溪窒。后面講繼續(xù)研究線程池和線程調(diào)度坤塞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市霉猛,隨后出現(xiàn)的幾起案子尺锚,更是在濱河造成了極大的恐慌,老刑警劉巖惜浅,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瘫辩,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機伐厌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進店門承绸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人挣轨,你說我怎么就攤上這事军熏。” “怎么了卷扮?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵荡澎,是天一觀的道長。 經(jīng)常有香客問我晤锹,道長摩幔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任鞭铆,我火速辦了婚禮或衡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘车遂。我一直安慰自己封断,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布舶担。 她就那樣靜靜地躺著坡疼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪柄沮。 梳的紋絲不亂的頭發(fā)上回梧,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天废岂,我揣著相機與錄音祖搓,去河邊找鬼。 笑死湖苞,一個胖子當(dāng)著我的面吹牛拯欧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播财骨,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼镐作,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了隆箩?” 一聲冷哼從身側(cè)響起该贾,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎捌臊,沒想到半個月后杨蛋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年逞力,在試婚紗的時候發(fā)現(xiàn)自己被綠了曙寡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡寇荧,死狀恐怖举庶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情揩抡,我是刑警寧澤户侥,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站峦嗤,受9級特大地震影響添祸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜寻仗,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一刃泌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧署尤,春花似錦耙替、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至箕别,卻和暖如春铜幽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背串稀。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工除抛, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人母截。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓到忽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親清寇。 傳聞我的和親對象是個殘疾皇子喘漏,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內(nèi)容