簡單聊聊 Java線程的基礎(chǔ)知識

哈嘍黎烈,大家好习柠,線程是Java中很重要的一個知識點匀谣,我相信大家都知道如何運(yùn)用多線程來處理任務(wù),但是其中有很多細(xì)節(jié)可能不是特別的明白资溃,我打算做一系列有關(guān)線程的文章武翎,就當(dāng)是個記錄,順便和大家分享一下有關(guān)線程的知識溶锭。

這篇文章我們先來講一講線程的基礎(chǔ)知識宝恶,那么下面直接開始。


進(jìn)程

一說到線程趴捅,那就不得不提進(jìn)程垫毙。這兩個概念很多人最開始容易混淆,而且面試的時候拱绑,有的面試官也會問到综芥。那么什么是進(jìn)程呢,進(jìn)程是程序關(guān)于某數(shù)據(jù)集合上的一次運(yùn)行活動猎拨,是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位膀藐,是操作系統(tǒng)結(jié)構(gòu)的基礎(chǔ)。

這樣說可能還是有點懵逼红省,舉個簡單的栗子额各,你在手機(jī)上啟動一個軟件,那么這個軟件就是一個進(jìn)程吧恃∠豪玻或者說你在電腦上打開QQ,那么這個QQ就是一個進(jìn)程蚜枢。

線程

進(jìn)程說完了缸逃,來說說線程,線程是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位厂抽。它被包含在進(jìn)程之中需频,是進(jìn)程中的實際運(yùn)作單位。線程可以在進(jìn)程中獨(dú)立運(yùn)行子任務(wù)筷凤,并且一個進(jìn)程至少有一個線程昭殉。

來舉個栗子,假如你在手機(jī)上啟動了QQ藐守,在QQ中你可以和好友聊天挪丢,下載文件,傳輸數(shù)據(jù)卢厂,其中每一項工作我們都可以理解為一個線程在執(zhí)行乾蓬。這些工作也可以同時執(zhí)行,當(dāng)它們同時進(jìn)行的時候我們可以理解為多個線程同時執(zhí)行慎恒,這也是線程的好處之一任内,同時處理多個任務(wù)撵渡,以節(jié)約時間。

多線程同時工作的時候其實是CPU在各個線程之間快速切換死嗦,速度很快趋距,使我們感覺是在同時進(jìn)行。

線程運(yùn)用

線程的調(diào)用大家肯定都很熟悉了越除,有兩種方法來調(diào)用線程執(zhí)行任務(wù)节腐,下面我們來分別講一講。

新建一個類并繼承Thread類

下面我們看下代碼

public class TestMain {

    public static void main(String[] args) {
        TestThread testThread = new TestThread();
        testThread.start();
    }

    public static class TestThread extends Thread {
        @Override
        public void run() {
            System.out.println("TestThread is run");
        }
    }
}

代碼大家肯定都很熟悉摘盆,需要注意的是start方法重復(fù)調(diào)用會報錯翼雀。
當(dāng)我們繼承Thread類的時候有一個不好的地方是Java并不能多繼承,這樣可能會影響代碼的靈活性孩擂,所以一般來說實現(xiàn)Runnable接口是一個更好的選擇锅纺。

新建一個類實現(xiàn)Runnable接口

我們在Thread源碼中看到,Thread的構(gòu)造函數(shù)可以傳入Runnable肋殴。

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

所以我們也可以新建一個類并實現(xiàn)Runnable接口傳入Thread中囤锉,并執(zhí)行該線程。

下面我們先看看代碼

public class TestMain {

    public static void main(String[] args) {
        Thread thread = new Thread(new TestThread());
        thread.start();
    }

    public static class TestThread implements Runnable{
        @Override
        public void run() {
            System.out.println("test is run");
        }
    }
}

這個相信大家也是寫了很多遍了护锤,沒什么好說的官地。

線程執(zhí)行不確定性

線程在執(zhí)行的過程中有不確定性,這里我們先來看個例子烙懦。

public class TestMain {

    public static void main(String[] args) {
        Thread thread = new Thread(new TestThread());
        thread.start();

        for (int i = 0; i < 100; i++) {
            System.out.println("main");
        }
    }

    public static class TestThread implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("thread");
            }
        }
    }
}

運(yùn)行結(jié)果

thread
thread
thread
main
main
main
main
main
thread
thread
thread

我們可以看到在運(yùn)行的結(jié)果中thread和main是交叉打印出來的驱入,并不是先執(zhí)行完thread或者main。當(dāng)我們調(diào)用start方法的時候氯析,會告訴"線程規(guī)劃器"這個線程已經(jīng)準(zhǔn)備好了亏较,等待調(diào)用線程對象的run方法。這個過程就是讓系統(tǒng)安排一個時間來調(diào)用該線程中的run方法掩缓,使線程得到運(yùn)行雪情,具有異步執(zhí)行的效果。所以我們會看到thread和main會交叉打印出來你辣。

線程安全

線程安全是線程知識里面一個重要的知識點巡通,簡單來說就是當(dāng)多個線程同時訪問同一個變量時,可能會造成變量的不同步舍哄。我們先來舉例宴凉,加入有5張門票,5個售票員表悬,每個售票員賣出一張門票弥锄,門票數(shù)量就少1。下面先看看代碼。

public class TestMain {

    private static int count = 5;

    public static void main(String[] args) {
        TestThread testThread = new TestThread();
        Thread a = new Thread(testThread, "A");
        Thread b = new Thread(testThread, "B");
        Thread c = new Thread(testThread, "C");
        Thread d = new Thread(testThread, "D");
        Thread e = new Thread(testThread, "E");
        a.start();
        b.start();
        c.start();
        d.start();
        e.start();
    }

    public static class TestThread extends Thread {

        @Override
        public void run() {
            count--;
            System.out.println(currentThread().getName() + "賣出一張票籽暇,還剩余:" + count);
        }
    }
}

代碼很簡單窘行,就是依照上面的栗子寫的,那么我們來看看運(yùn)行結(jié)果

A賣出一張票图仓,還剩余:3
B賣出一張票,還剩余:3
C賣出一張票但绕,還剩余:2
D賣出一張票救崔,還剩余:1
E賣出一張票,還剩余:0

我們可以看到結(jié)果中出現(xiàn)了兩個3捏顺,這個是因為A,B同時訪問了這個變量造成的六孵。這就是線程安全問題,那么我們?nèi)绾谓鉀Q這個問題呢幅骄。Java給我們提供了synchronized字符劫窒,我們先來修改一下代碼。

    public static class TestThread extends Thread {

        @Override
        synchronized public void run() {
            count--;
            System.out.println(currentThread().getName() + "賣出一張票拆座,還剩余:" + count);
        }
    }

我們在run方法前面加入synchronized主巍。下面我們來看看運(yùn)行結(jié)果。

B賣出一張票挪凑,還剩余:4
C賣出一張票孕索,還剩余:3
A賣出一張票,還剩余:2
D賣出一張票躏碳,還剩余:1
E賣出一張票搞旭,還剩余:0

結(jié)果中并沒有重復(fù)的數(shù)字出現(xiàn)。當(dāng)在run方法前面加入synchronized的時菇绵,運(yùn)行到run方法肄渗,會先去判斷run方法是否有加鎖,如果加鎖了咬最,證明別的線程在調(diào)用這個方法翎嫡,就先等待其他線程調(diào)用完畢后再執(zhí)行這個方法。這樣run方法就是排隊執(zhí)行完成的永乌,所以結(jié)果正常钝的,沒有同時訪問同一個變量。當(dāng)運(yùn)行run方法的時候铆遭,如果沒有加鎖硝桩,那么線程會去拿這個鎖,注意這里是所有線程同時搶這把鎖枚荣,誰搶到了就先執(zhí)行誰的run方法碗脊。

isAlive

isAlive方法是判斷線程是否處于激活狀態(tài)。我們先來看看代碼

public class TestMain {

    public static void main(String[] args) {
        TestThread testThread = new TestThread();
        System.out.println("start testThread isAlive = " + testThread.isAlive());
        testThread.start();
        System.out.println("end testThread isAlive = " + testThread.isAlive());
    }

    public static class TestThread extends Thread {

        @Override
        public void run() {
            System.out.println("testThread isAlive = " + currentThread().isAlive());
        }
    }
}

運(yùn)行結(jié)果為

start testThread isAlive = false
end testThread isAlive = true
testThread isAlive = true

我們可以發(fā)現(xiàn)當(dāng)調(diào)用start方法過后,線程就處于激活狀態(tài)了衙伶。因為這里end在線程執(zhí)行完成之前就打印了祈坠,所以也是true,如果我們修改下代碼矢劲,那么end就可能為false了赦拘。

public static void main(String[] args) {
        try {
            TestThread testThread = new TestThread();
            System.out.println("start testThread isAlive = " + testThread.isAlive());
            testThread.start();
            Thread.sleep(1000);
            System.out.println("end testThread isAlive = " + testThread.isAlive());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

結(jié)果為:

start testThread isAlive = false
testThread isAlive = true
end testThread isAlive = false

下面我們再看一個有趣的栗子:

public class TestMain {

    public static void main(String[] args) {
        TestThread testThread = new TestThread();
        Thread thread = new Thread(testThread);
        thread.start();
    }

    public static class TestThread extends Thread {

        @Override
        public void run() {
            System.out.println("currentThread isAlive = " + currentThread().isAlive());
            System.out.println("this isAlive = " + this.isAlive());
        }
    }
}

這個運(yùn)行結(jié)果為:

currentThread isAlive = true
this isAlive = false

這里第一個為true我相信大家都可以理解,那么為什么第二個為false呢芬沉。這個就要說說currentThread這個方法了躺同,這個方法獲取的是在哪個線程中運(yùn)行,而this獲取的是當(dāng)前線程丸逸。因為testThread 是以參數(shù)傳入到了Thread中蹋艺,在Thread中并不是像線程調(diào)用start方法那樣來運(yùn)行run方法的。而是直接調(diào)用run方法黄刚,所以this.isAlive()獲取的當(dāng)前線程并沒有調(diào)用start方法捎谨,所以為false。而currentThread獲取的是運(yùn)行的線程憔维,所以結(jié)果為true涛救。

線程停止

線程的停止我們主要來講一講interrupt方法。我們先來看一段代碼:

public class TestMain {

    public static void main(String[] args) {
        TestThread testThread = new TestThread();
        testThread.start();
        testThread.interrupt();
    }

    public static class TestThread extends Thread {

        @Override
        public void run() {
            for (int i = 0; i < 50000; i++) {
                System.out.println(i);
            }
        }
    }
}

我們在線程調(diào)用start方法過后馬上又調(diào)用了interrupt方法业扒,按理來說線程應(yīng)該立馬停止州叠,那么我們看看結(jié)果:

.......
49997
49998
49999

最后我們可以看到線程是完完整整執(zhí)行完成了的。難道interrupt方法沒有作用嗎凶赁?我們先來看看另外兩個方法

    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

    public boolean isInterrupted() {
        return isInterrupted(false);
    }

這兩個方法都是判斷線程是否已經(jīng)中斷咧栗。第一個方法是一個靜態(tài)方法,并且在方法中調(diào)用了currentThread方法虱肄,所以它判斷的是當(dāng)前運(yùn)行的線程是否已經(jīng)中斷致板。第二個方法是判斷線程對象是否已經(jīng)中斷。我們發(fā)現(xiàn)他們最終都是調(diào)用了同一個方法咏窿,我們先來看看這個方法:

    /**
     * Tests if some Thread has been interrupted.  The interrupted state
     * is reset or not based on the value of ClearInterrupted that is
     * passed.
     */
    private native boolean isInterrupted(boolean ClearInterrupted);

這是一個native方法斟或,傳入的參數(shù)是指是否清除線程的中斷狀態(tài)。true為清除集嵌,false為不清除萝挤。我們在代碼中加入判斷試試

public static void main(String[] args) {
        TestThread testThread = new TestThread();
        testThread.start();
        testThread.interrupt();
        System.out.println("線程是否中斷:" + testThread.isInterrupted());
    }

結(jié)果為:

線程是否中斷:true

我們可以發(fā)現(xiàn)在調(diào)用interrupt方法過后其實是給線程加了一個中斷的標(biāo)識,我們調(diào)用isInterrupted方法就可以看出根欧。那么我們就可以運(yùn)用這個特性怜珍,讓線程實現(xiàn)真正的中斷。下面來看看修改的代碼:

public class TestMain {

    public static void main(String[] args) {
        try {
            TestThread testThread = new TestThread();
            testThread.start();
            Thread.sleep(200);
            testThread.interrupt();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static class TestThread extends Thread {

        @Override
        public void run() {
            for (int i = 0; i < 50000; i++) {
                if (isInterrupted()) {
                    System.out.println("線程已經(jīng)終止");
                    break;
                }
                System.out.println(i);
            }
        }
    }
}

結(jié)果為:

......
35289
35290
線程已經(jīng)終止

我們可以發(fā)現(xiàn)線程進(jìn)入了中斷判斷并跳出了for循環(huán)凤粗。這樣雖然可以終止for循環(huán)酥泛,但是for循環(huán)以下的代碼依然會執(zhí)行,有的人肯定會想到用return,這樣也是可以的柔袁,并且不會執(zhí)行for循環(huán)下面的代碼呆躲,但是return太多會造成代碼污染,這里我們推薦另一個方法捶索。先來看看代碼:

    public static class TestThread extends Thread {

        @Override
        public void run() {
            try {
                for (int i = 0; i < 50000; i++) {
                    if (isInterrupted()) {
                        System.out.println("線程已經(jīng)終止");
                        throw new InterruptedException();
                    }
                    System.out.println(i);
                }
                System.out.println("for循環(huán)后面的代碼");
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("進(jìn)入catch:" + e.toString());
            }
        }
    }

我們利用try catch來停止線程插掂,并可以在catch中做一些釋放等操作。
結(jié)果為:

......
39160
線程已經(jīng)終止
java.lang.InterruptedException
    at test.TestMain$TestThread.run(TestMain.java:24)
進(jìn)入catch:java.lang.InterruptedException

yield()

yield()方法的作用是先放棄當(dāng)前的CPU資源腥例,讓其他線程去占用CPU執(zhí)行時間辅甥。但是放棄的時間不確定,可能剛剛放棄馬上又占有CPU資源了院崇。下面我們舉個栗子:

public class TestMain {

    public static void main(String[] args) {
        TestThread1 testThread1 = new TestThread1();
        testThread1.start();
        TestThread2 testThread2 = new TestThread2();
        testThread2.start();
    }

    public static class TestThread1 extends Thread {

        @Override
        public void run() {
            long start = System.currentTimeMillis();
            for (int i = 0; i < 50000; i++) {
                int a = i;
            }
            long end = System.currentTimeMillis();
            System.out.println("1使用時間為:" + (end - start));
        }
    }

    public static class TestThread2 extends Thread {

        @Override
        public void run() {
            long start = System.currentTimeMillis();
            for (int i = 0; i < 50000; i++) {
                yield();
                int a = i;
            }
            long end = System.currentTimeMillis();
            System.out.println("2使用時間為:" + (end - start));
        }
    }

}

結(jié)果為:

1使用時間為:1
2使用時間為:8

我們可以明顯的看到2使用的時間長于1使用的時間。

線程優(yōu)先級

在線程中有一個方法可以設(shè)置線程的優(yōu)先級

public final void setPriority(int newPriority)

Java線程中線程分為1-10個等級袍祖,等級越高底瓣,線程被執(zhí)行的幾率也就越大,這里要注意是執(zhí)行的幾率蕉陋,而不是優(yōu)先級高的就比優(yōu)先級低的先執(zhí)行捐凭。

另外線程的優(yōu)先級是有傳遞效果的,舉個栗子凳鬓,A線程啟動B線程茁肠,如果A線程優(yōu)先級為5,那么B線程的優(yōu)先級也為5缩举。

守護(hù)線程

守護(hù)線程可能大家平時都沒有怎么用垦梆,我們平時經(jīng)常使用的是用戶線程,守護(hù)線程是一個特殊的線程仅孩,當(dāng)我們進(jìn)程中沒有用戶線程的時候托猩,守護(hù)線程就會自動銷毀。Java中典型的守護(hù)線程就是垃圾回收線程辽慕。下面我們來舉個栗子:

public class TestMain {

    public static void main(String[] args) {
        try {
            TestThread1 testThread1 = new TestThread1();
            testThread1.setDaemon(true);
            testThread1.start();
            Thread.sleep(1000);
            System.out.println("end");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static class TestThread1 extends Thread {

        @Override
        public void run() {
            try {
                int i = 0;
                while (true) {
                    i++;
                    System.out.println(i);
                    Thread.sleep(200);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

結(jié)果為:

1
2
3
4
5
end

而當(dāng)我們?nèi)サ魌estThread1.setDaemon(true);這句代碼京腥,結(jié)果為:

......
3
4
5
end
6
7
......

這樣我們就發(fā)現(xiàn)當(dāng)線程為守護(hù)線程的時候,main結(jié)束了溅蛉,守護(hù)線程也就結(jié)束了公浪,如果不是守護(hù)線程,則會一直執(zhí)行船侧。


到這里線程的基礎(chǔ)就講完了欠气,上文中有錯誤的地方歡迎大家指出。

3Q

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末镜撩,一起剝皮案震驚了整個濱河市晃琳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖卫旱,帶你破解...
    沈念sama閱讀 212,029評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件人灼,死亡現(xiàn)場離奇詭異,居然都是意外死亡顾翼,警方通過查閱死者的電腦和手機(jī)投放,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來适贸,“玉大人灸芳,你說我怎么就攤上這事“葑耍” “怎么了烙样?”我有些...
    開封第一講書人閱讀 157,570評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蕊肥。 經(jīng)常有香客問我谒获,道長,這世上最難降的妖魔是什么壁却? 我笑而不...
    開封第一講書人閱讀 56,535評論 1 284
  • 正文 為了忘掉前任批狱,我火速辦了婚禮,結(jié)果婚禮上展东,老公的妹妹穿的比我還像新娘赔硫。我一直安慰自己,他們只是感情好盐肃,可當(dāng)我...
    茶點故事閱讀 65,650評論 6 386
  • 文/花漫 我一把揭開白布爪膊。 她就那樣靜靜地躺著,像睡著了一般砸王。 火紅的嫁衣襯著肌膚如雪惊完。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,850評論 1 290
  • 那天处硬,我揣著相機(jī)與錄音小槐,去河邊找鬼。 笑死荷辕,一個胖子當(dāng)著我的面吹牛凿跳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播疮方,決...
    沈念sama閱讀 39,006評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼控嗜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了骡显?” 一聲冷哼從身側(cè)響起疆栏,我...
    開封第一講書人閱讀 37,747評論 0 268
  • 序言:老撾萬榮一對情侶失蹤曾掂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后壁顶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體珠洗,經(jīng)...
    沈念sama閱讀 44,207評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,536評論 2 327
  • 正文 我和宋清朗相戀三年若专,在試婚紗的時候發(fā)現(xiàn)自己被綠了许蓖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,683評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡调衰,死狀恐怖膊爪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嚎莉,我是刑警寧澤米酬,帶...
    沈念sama閱讀 34,342評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站趋箩,受9級特大地震影響赃额,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜阁簸,卻給世界環(huán)境...
    茶點故事閱讀 39,964評論 3 315
  • 文/蒙蒙 一爬早、第九天 我趴在偏房一處隱蔽的房頂上張望哼丈。 院中可真熱鬧启妹,春花似錦、人聲如沸醉旦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,772評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽车胡。三九已至檬输,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間匈棘,已是汗流浹背丧慈。 一陣腳步聲響...
    開封第一講書人閱讀 32,004評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留主卫,地道東北人逃默。 一個月前我還...
    沈念sama閱讀 46,401評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像簇搅,于是被迫代替她去往敵國和親完域。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,566評論 2 349

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

  • Java多線程學(xué)習(xí) [-] 一擴(kuò)展javalangThread類 二實現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 2,952評論 1 18
  • 本文主要講了java中多線程的使用方法瘩将、線程同步吟税、線程數(shù)據(jù)傳遞凹耙、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等肠仪。 首先講...
    李欣陽閱讀 2,444評論 1 15
  • 一肖抱、進(jìn)程和線程 進(jìn)程 進(jìn)程就是一個執(zhí)行中的程序?qū)嵗總€進(jìn)程都有自己獨(dú)立的一塊內(nèi)存空間藤韵,一個進(jìn)程中可以有多個線程虐沥。...
    阿敏其人閱讀 2,611評論 0 13
  • 整理來自互聯(lián)網(wǎng) 1,JDK:Java Development Kit泽艘,java的開發(fā)和運(yùn)行環(huán)境欲险,java的開發(fā)工具...
    Ncompass閱讀 1,537評論 0 6
  • 一、線程概念 1. 操作系統(tǒng)中的線程現(xiàn)在的操作系統(tǒng)是多任務(wù)操作系統(tǒng)匹涮,多線程是實現(xiàn)多任務(wù)的一種方式天试,在操作系統(tǒng)中,每...
    TyiMan閱讀 1,906評論 1 35