Java多線程基礎(chǔ)-使用多線程

*本篇文章已授權(quán)微信公眾號(hào)guolin_blog (郭霖)獨(dú)家發(fā)布

線程:程序執(zhí)行流的最小單元【可以理解為:進(jìn)程中獨(dú)立運(yùn)行的子任務(wù)】。
多線程優(yōu)點(diǎn):最大限度的利用CPU的空閑時(shí)間來(lái)處理其他任務(wù)冈敛。


|-目錄
|??創(chuàng)建線程
|??線程運(yùn)行結(jié)果與執(zhí)行順序無(wú)關(guān)
|??線程實(shí)例變量與安全問(wèn)題
|??停止線程
|??線程優(yōu)先級(jí)
|??守護(hù)線程
|??線程讓步


-創(chuàng)建線程

?線程的創(chuàng)建方式:
?1.繼承Thread類(lèi)

public class ThreadCreateDemo1 {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start(); //該方法調(diào)用多次,出現(xiàn)IllegalThreadStateException
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        super.run();
        System.out.println("hellow_world!");
    }
}

?2.實(shí)現(xiàn)Runnable接口

public class ThreadCreateDemo2 {
    public static void main(String[] args) {
        Runnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

class MyRunnable implements Runnable {
    public void run() {
        System.out.println("通過(guò)Runnable創(chuàng)建的線程!");
    }
}

??上述兩種創(chuàng)建方式,工作時(shí)性質(zhì)一樣隘蝎。但是建議使用實(shí)現(xiàn)Runable接口方式枫耳。解決單繼承的局限性歹嘹。

-線程運(yùn)行結(jié)果與執(zhí)行順序無(wú)關(guān)

?線程的調(diào)度是由CPU決定,CPU執(zhí)行子任務(wù)時(shí)間具有不確定性礁扮。

public class ThreadRandomDemo1 {
    public static void main(String[] args) {
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new RandomThread("RandomThread:" + i);
        }
        for(Thread thread : threads) {
            thread.start();
        }
    }
}

class RandomThread extends Thread {
    
    public RandomThread(String name) {
        super(name);
    }
    
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

??以上10個(gè)線程呀伙,代碼按照順序執(zhí)行补履,但是結(jié)果可以看出沒(méi)有按照順序執(zhí)行,而且多次執(zhí)行結(jié)果基本不同剿另。


圖1-1 隨機(jī)線程被執(zhí)行

-線程實(shí)例變量與安全問(wèn)題

??線程之間變量有共享與不共享之分箫锤,共享理解為大家都使用同一份,不共享理解為每個(gè)單獨(dú)持有一份雨女。
??1.共享數(shù)據(jù)情況

public class ThreadShareVariableDemo {
    public static void main(String[] args) {
        Runnable runnable = new ShareVariableRunnable();
        Thread[] threads = new Thread[5];
        for (int i = 0; i < 5; i++) {
            threads[i] = new Thread(runnable, "thread:" + (i+1));
        }
        for (Thread thread : threads) {
            thread.start();
        }
    }
}

class ShareVariableRunnable implements Runnable {
    private int count = 5;
    
    public void run() {
        System.out.println("" + Thread.currentThread().getName() + ",count:" + count--);
    }
}
圖1-2 線程共享變量

??從上圖結(jié)果可以看出谚攒,count變量是共享的,不然都會(huì)打印5戚篙。但是也發(fā)現(xiàn)了一點(diǎn)thread:1 與 thread:2 打印值一樣五鲫,該現(xiàn)象就是我們通常稱(chēng)為的臟數(shù)據(jù)【多線程對(duì)同一變量進(jìn)行讀寫(xiě)操作不同步產(chǎn)生】。
??解決方案在訪問(wèn)變量方法中增加synchronized關(guān)鍵字:

class ShareVariableRunnable implements Runnable {
    private int count = 5;
    
    public synchronized void run() {
        System.out.println("" + Thread.currentThread().getName() + ",count:" + count--);
    }
}
圖1-3 線程共享變量安全

??如圖每次打印count都是正常遞減岔擂,這里解釋一下synchronized關(guān)鍵字位喂,含有synchronized關(guān)鍵字的這個(gè)方法稱(chēng)為“互斥區(qū)” 或“臨界區(qū)”浪耘,只有獲得這個(gè)關(guān)鍵字對(duì)應(yīng)的鎖才能執(zhí)行方法體,方法體執(zhí)行完自動(dòng)會(huì)釋放鎖塑崖。

-停止線程

?終止正在運(yùn)行的線程方法有三種:
??1)使用退出標(biāo)志,使線程正常的執(zhí)行完run方法終止七冲。
??2)使用interrupt方法,使線程異常,線程進(jìn)行捕獲或拋異常规婆,正常執(zhí)行完run方法終止澜躺。
??3)使用stop方法強(qiáng)制退出。
?這里主要說(shuō)明前兩種方法抒蚜;
?1.使用退出標(biāo)志方法

public class ThreadVariableStopDemo {
    public static void main(String[] args) throws InterruptedException {
        VariableStopThread thread = new VariableStopThread("thread_1");
        thread.start();
        Thread.sleep(1);
        thread.Stop();
    }
}

class VariableStopThread extends Thread {
    private boolean interrupt = true;
    
    public VariableStopThread(String name) {
        super(name);
    }
    
    public void run() {
        System.out.println(Thread.currentThread().getName() + ":線程開(kāi)始運(yùn)行!");
        int i = 0;
        while(interrupt) {
            System.out.println("" + (i++));
        }
        System.out.println("我停止了! timer:" + System.currentTimeMillis());
    }
    
    public void Stop() {
        System.out.println(Thread.currentThread().getName() + ":線程設(shè)置了停止! timer:" + System.currentTimeMillis());
        this.interrupt = false;
    }
}
圖1-4 線程退出

??Thread_1中啟動(dòng)了一個(gè)while循環(huán),一直打印i的累加值掘鄙。main線程在sleep 1ms后設(shè)置Thread_1停止標(biāo)志。Thread_1 while循環(huán)判斷條件不符合正常執(zhí)行完run方法結(jié)束嗡髓。從【圖1-4 線程退出】中可以看出設(shè)置完停止標(biāo)志后13還是正常打印操漠,原因是因?yàn)閣hile方法體中是原子操作,不能直接打斷饿这。
??在使用終止線程方法一時(shí)浊伙,個(gè)人建議代碼這么修改更符合Java API規(guī)范也避免線程死循環(huán)問(wèn)題【后面章節(jié)會(huì)介紹】。

public class ThreadVariableStopDemo {
    public static void main(String[] args) throws InterruptedException {
        VariableStopThread thread = new VariableStopThread("thread_1");
        thread.start();
        Thread.sleep(10);
        thread.interrupt();
    }
}

class VariableStopThread extends Thread {
    
    public VariableStopThread(String name) {
        super(name);
    }
    
    public void run() {
        System.out.println(Thread.currentThread().getName() + ":線程開(kāi)始運(yùn)行!");
        while(!isInterrupted()) { 
        }
        System.out.println("我停止了! timer:" + System.currentTimeMillis());
    }
    
}

?2.使用interrupt方法

public class ThreadInterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new InterruptThread("thread_1");
        thread.start();
        Thread.sleep(1);
        System.out.println(thread.getName() + "線程設(shè)置:interrupt");
        thread.interrupt();
    }
}

class InterruptThread extends Thread {
    
    public InterruptThread(String name) {
        super(name);
    }
    
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "線程開(kāi)始!");
        for(int i =0; i < 1000; i++) {
            try {
                Thread.sleep(0);
                System.out.println("" + (i + 1));
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "線程捕獲異常,退出循環(huán)!");
                break;
            }
        }
        System.out.println(Thread.currentThread().getName() + "線程結(jié)束!");
    }
}

圖1-5 線程退出

??從上圖可以看出線程正常退出长捧,但是發(fā)現(xiàn)一點(diǎn)循環(huán)結(jié)構(gòu)體后面一句打印也打印了嚣鄙,解決這個(gè)問(wèn)題的方案有兩個(gè):
?1.異常法

@Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "線程開(kāi)始!");
        try {
            for(int i = 0; i < 1000; i++) {
                if(Thread.currentThread().interrupted()) {
                    System.out.println(Thread.currentThread().getName() + "線程停止?fàn)顟B(tài)!");
                    throw new InterruptedException();
                }
                Thread.sleep(0);
                System.out.println("" + (i + 1));
            }
            System.out.println(Thread.currentThread().getName() + "線程結(jié)束!");
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + "線程捕獲異常,退出循環(huán)!");
            e.printStackTrace();
        }
    }

圖1-6 線程退出

?代碼有兩個(gè)關(guān)鍵點(diǎn):
??1)for循環(huán)外捕獲異常【這是程序的關(guān)鍵點(diǎn)】
??2)判斷設(shè)置了interrupted標(biāo)志則拋出異常串结。
?2.return法

@Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "線程開(kāi)始!");
        try {
            for(int i = 0; i < 1000; i++) {
                Thread.sleep(0);
                System.out.println("" + (i + 1));
            }
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + "線程捕獲異常,退出循環(huán)!");
            e.printStackTrace();
            return;
        }
        System.out.println(Thread.currentThread().getName() + "線程結(jié)束!");
    }

??這個(gè)方法相對(duì)簡(jiǎn)單哑子,也比較常用。兩種方法結(jié)果都一樣直接退出不進(jìn)行后續(xù)工作,兩種方法依據(jù)功能需求選擇奉芦。
??上述兩個(gè)方法sleep都是0赵抢,這里給大家看看沉睡中退出,有一個(gè)現(xiàn)象會(huì)發(fā)生声功。

圖1-7 線程退出

??從【圖1-7 線程退出】可以看出sleep使用interrupt()退出直接進(jìn)入異常,而且interrupt標(biāo)志位置為false【記住這點(diǎn)】宠叼。

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

??線程優(yōu)先級(jí)范圍為1-10先巴,API提供等級(jí)分為:低(MIN_PRIORITY = 1),中(NORM_PRIORITY=5)冒冬,高(MAX_PRIORITY=10)伸蚯。
?線程優(yōu)先級(jí)有以下特點(diǎn):
??1)繼承特性【線程A中啟動(dòng)線程B,線程B繼承了A的優(yōu)先級(jí)】简烤;
??2)隨機(jī)性【線程調(diào)度的順序不一定是根據(jù)優(yōu)先級(jí)剂邮,具有隨機(jī)性】;

public class ThreadPriorityDemo {
    public static void main(String[] args) {
        Thread thread = new ThreadPriority("thread_1<<<<");
        Thread thread_1 = new ThreadPriority(">>>thread_2");
        thread_1.setPriority(Thread.MIN_PRIORITY); //<設(shè)置線程優(yōu)先級(jí)
        thread.setPriority(Thread.MAX_PRIORITY);
        thread_1.start();
        thread.start();
    }
}

class ThreadPriority extends Thread {
    public ThreadPriority(String name) {
        super(name);
    }
    
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("" + Thread.currentThread().getName() + ",number:" + i + ",Priority:" + Thread.currentThread().getPriority());
        }
    }
}

圖1-8 線程優(yōu)先級(jí)

??運(yùn)行的很給力横侦,以下體現(xiàn)了兩個(gè)問(wèn)題:①線程運(yùn)行順序與代碼執(zhí)行順序無(wú)關(guān)挥萌。②線程優(yōu)先級(jí)具有隨機(jī)性绰姻,不是優(yōu)先級(jí)高的就先完成。
?下面驗(yàn)證線程優(yōu)先級(jí)具有繼承性,上面代碼修改如下:

public static void main(String[] args) {
        Thread thread = new ThreadPriority("thread_1<<<<");
        Thread thread_1 = new ThreadPriority(">>>thread_2");
//      thread_1.setPriority(Thread.MIN_PRIORITY); //<取消設(shè)置線程優(yōu)先級(jí)
        thread.setPriority(Thread.MAX_PRIORITY);
        thread_1.start();
        thread.start();
    }
圖1-9 線程優(yōu)先級(jí)

??從上圖可以看出thread_2與main線程優(yōu)先級(jí)一樣都是5引瀑,原因是main線程中啟動(dòng)了thread_2狂芋,thread_2繼承了mian線程的優(yōu)先級(jí)。

-守護(hù)線程

守護(hù)線程顧名思義是一個(gè)線程守護(hù)另一個(gè)線程【此線程為非守護(hù)線程】憨栽,故守護(hù)的線程稱(chēng)為守護(hù)線程帜矾,被守護(hù)的線程稱(chēng)為非守護(hù)線程。作用是為其他線程運(yùn)行提供便利服務(wù)屑柔。

public class DaemonThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new DaemonThread();
        thread.setDaemon(true);
        thread.start();
        System.out.println("" + Thread.currentThread().getName() + "停止運(yùn)行!" );
    }
}

class DaemonThread extends Thread {
    @Override
    public void run() {
        while (true) {
            System.out.println("DaemonThread 正在運(yùn)行!");
        }
    }
}
圖1-10 守護(hù)線程

??從上圖可以看出屡萤,主線程停止DaemonThread線程也相應(yīng)的停止了,但不是立即停止掸宛。

-線程讓步

??線程讓步【yield方法】讓當(dāng)前線程釋放CPU資源死陆,讓其他線程搶占。

public class ThreadYieldDemo {
    public static void main(String[] args) {
        Thread thread = new ThreadYield();
        thread.start();
    }
}
class ThreadYield extends Thread {
    @Override
    public void run() {
        long time_start = System.currentTimeMillis();
        for(int i = 0; i < 500000; i++) {
            Math.random();
//          Thread.yield();
        }
        long time_end = System.currentTimeMillis();
        System.out.println("用時(shí):" + (time_end - time_start));
    }
}
圖1-11 線程正常耗時(shí).jpg

圖1-11 線程讓步耗時(shí).jpg

??從以上兩圖可以看出旁涤,線程的讓步操作比不讓步耗時(shí)長(zhǎng)翔曲。

-總結(jié)

??本篇主要介紹線程API的基礎(chǔ)功能,比較常用的線程創(chuàng)建,線程安全,停止線程劈愚。只有掌握這些基礎(chǔ)才能更好的服務(wù)后面線程知識(shí)瞳遍。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市菌羽,隨后出現(xiàn)的幾起案子掠械,更是在濱河造成了極大的恐慌,老刑警劉巖注祖,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件猾蒂,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡是晨,警方通過(guò)查閱死者的電腦和手機(jī)肚菠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)罩缴,“玉大人蚊逢,你說(shuō)我怎么就攤上這事◇镎拢” “怎么了烙荷?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)檬寂。 經(jīng)常有香客問(wèn)我终抽,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任昼伴,我火速辦了婚禮匾旭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘亩码。我一直安慰自己季率,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布描沟。 她就那樣靜靜地躺著飒泻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪吏廉。 梳的紋絲不亂的頭發(fā)上泞遗,一...
    開(kāi)封第一講書(shū)人閱讀 52,246評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音席覆,去河邊找鬼史辙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛佩伤,可吹牛的內(nèi)容都是我干的聊倔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼生巡,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼耙蔑!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起孤荣,我...
    開(kāi)封第一講書(shū)人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤甸陌,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后盐股,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體钱豁,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年疯汁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了牲尺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡幌蚊,死狀恐怖秸谢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情霹肝,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布塑煎,位于F島的核電站沫换,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜讯赏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一垮兑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧漱挎,春花似錦系枪、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至膊夹,卻和暖如春衬浑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背放刨。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工工秩, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人进统。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓助币,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親螟碎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子眉菱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

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