国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Thread類源碼解讀(1)——如何創(chuàng)建和啟動(dòng)線程

stefanieliang / 3002人閱讀

摘要:無(wú)論它由子類覆寫(xiě)提供還是由對(duì)象提供,方法最終都會(huì)新建一個(gè)線程來(lái)執(zhí)行這個(gè)方法。這種方法看上去好像復(fù)雜了好多,但其實(shí)就是通過(guò)新建類的對(duì)象來(lái)創(chuàng)建線程。總結(jié)在中,創(chuàng)建一個(gè)線程,有且僅有一種方式創(chuàng)建一個(gè)類實(shí)例,并調(diào)用它的方法。

前言

系列文章目錄

談到線程同步與通信,線程本身的概念是繞不開(kāi)的,而進(jìn)程和線程的概念已經(jīng)是老生常談的話題了,一些基本的概念本文就不再討論了,本篇僅僅致力于通過(guò)源碼,了解線程的構(gòu)造與啟動(dòng),從而更深入的了解線程。

本文源碼基于jdk1.8 。

閱讀完本文,你應(yīng)當(dāng)有能力回答以下常見(jiàn)面試題:

創(chuàng)建線程有哪幾種方式?

如何啟動(dòng)一個(gè)線程?

線程的run方法和start方法有什么區(qū)別?

Runnale接口

我們看Thread類的定義知道,它實(shí)現(xiàn)了Runable接口

public class Thread implements Runnable {
    ...
}

Runnable接口的定義如下:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

它只有一個(gè)抽象方法run。同時(shí),該接口還被@FunctionalInterface注解標(biāo)注,說(shuō)明它是一個(gè)函數(shù)式接口(@FunctionalInterface是java 1.8版本之后引入的)。這意味著我們可以使用Lambda表達(dá)式來(lái)創(chuàng)建Runnable接口的實(shí)例,這個(gè)我們到后面再舉例。

線程創(chuàng)建

在java中,創(chuàng)建一個(gè)線程,有且僅有一種方式:

創(chuàng)建一個(gè)Thread類實(shí)例,并調(diào)用它的start方法。

這寫(xiě)在了java語(yǔ)言規(guī)范中(參見(jiàn)The Java Language Specification, Java SE 8 Edition, P659,chapter17):

Threads are represented by the Thread class. The only way for a user to create a thread is to create an object of this class; each thread is associated with such an object. A thread will start when the start() method is invoked on the corresponding Thread object.
構(gòu)造函數(shù)

要?jiǎng)?chuàng)建一個(gè)Thread類的實(shí)例自然要通過(guò)構(gòu)造函數(shù),Thread的public構(gòu)造函數(shù)有8個(gè)之多,但是他們本質(zhì)上都調(diào)用了同一個(gè)init函數(shù):

public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
    init(null, null, name, 0);
}
public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target, String name) {
    init(null, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target) {
    init(group, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(ThreadGroup group, String name) {
    init(group, null, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name) {
    init(group, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name, long stackSize) {
    init(group, target, name, stackSize);
}

可見(jiàn),這八個(gè)public類型的構(gòu)造函數(shù)只不過(guò)是給init的方法的四個(gè)參數(shù)分別賦不同的值, 這四個(gè)參數(shù)分別是:

ThreadGroup g(線程組)

Runnable target (Runnable 對(duì)象)

String name (線程的名字)

long stackSize (為線程分配的棧的大小,若為0則表示忽略這個(gè)參數(shù))

而init方法又調(diào)用了另一個(gè)init方法,設(shè)置了AccessController,以及inheritThreadLocals參數(shù):

/**
 * Initializes a Thread with the current AccessControlContext.
 * @see #init(ThreadGroup,Runnable,String,long,AccessControlContext,boolean)
 */
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
    init(g, target, name, stackSize, null, true);
}

//上面那個(gè)init方法最終調(diào)用了下面這個(gè)方法:

/**
 * Initializes a Thread.
 *
 * @param g the Thread group
 * @param target the object whose run() method gets called
 * @param name the name of the new Thread
 * @param stackSize the desired stack size for the new thread, or
 *        zero to indicate that this parameter is to be ignored.
 * @param acc the AccessControlContext to inherit, or
 *            AccessController.getContext() if null
 * @param inheritThreadLocals if {@code true}, inherit initial values for
 *            inheritable thread-locals from the constructing thread
 */
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
    ...
}

init方法中有一些關(guān)于線程組和訪問(wèn)控制上下文的設(shè)置,這里我們暫時(shí)就不深入討論了。

所以綜上來(lái)看,我們最常用的也就兩個(gè)參數(shù):

Runnable target (Runnable 對(duì)象)

String name (線程的名字)

而對(duì)于線程的名字,其默認(rèn)值為"Thread-" + nextThreadNum(), nextThreadNum方法又是什么呢:

/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}

可見(jiàn),它就是一個(gè)簡(jiǎn)單的遞增計(jì)數(shù)器,所以如果創(chuàng)建線程時(shí)沒(méi)有指定線程名,那線程名就會(huì)是:
Thread-0, Thread-1, Thread-2, Thread-3, ...

至此,我們看到,雖然Thread類的構(gòu)造函數(shù)有這么多,但對(duì)我們來(lái)說(shuō)真正重要的參數(shù)只有一個(gè):

Runnable target (Runnable 對(duì)象)

所以創(chuàng)建一個(gè)線程實(shí)例最重要的是要傳入一個(gè)Runnable類型對(duì)象。

既然是Runnable類型,那么這個(gè)target必然是實(shí)現(xiàn)了Runnable接口的,也就是說(shuō)該對(duì)象一定覆寫(xiě)了run方法。

我們知道,Thread類本身也實(shí)現(xiàn)了Runnable接口,所以它必然也覆寫(xiě)了run方法,我們先來(lái)看看它的run方法:

@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

可以看到,這個(gè)run方法僅僅是調(diào)用了target對(duì)象的run方法,如果我們?cè)诰€程構(gòu)造時(shí)沒(méi)有傳入target(例如調(diào)用了無(wú)參構(gòu)造函數(shù)),那么這個(gè)run方法就什么也不會(huì)做。

啟動(dòng)線程

線程對(duì)象創(chuàng)建完了之后,接下來(lái)就是啟動(dòng)一個(gè)線程,在java中,啟動(dòng)一個(gè)線程必須調(diào)用線程的start方法:

/**
 * Causes this thread to begin execution; the Java Virtual Machine
 * calls the run method of this thread.
 * 

* The result is that two threads are running concurrently: the * current thread (which returns from the call to the * start method) and the other thread (which executes its * run method). *

* It is never legal to start a thread more than once. * In particular, a thread may not be restarted once it has completed * execution. * * @exception IllegalThreadStateException if the thread was already * started. * @see #run() * @see #stop() */ public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group"s list of threads * and the group"s unstarted count can be decremented. */ group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } private native void start0()

這個(gè)方法本質(zhì)是調(diào)用了native的start0()方法,但是它的注釋部分說(shuō)明一些很重要的信息:

這個(gè)方法使得線程開(kāi)始執(zhí)行,并由JVM來(lái)執(zhí)行這個(gè)線程的run方法,結(jié)果就是有兩個(gè)線程在并發(fā)執(zhí)行,一個(gè)是當(dāng)前線程,也就是調(diào)用了Thread#start方法的線程,另一個(gè)線程就是當(dāng)前thread對(duì)象代表的線程,它執(zhí)行了run方法。

也就是說(shuō),這個(gè)Thread類實(shí)例代表的線程最終會(huì)執(zhí)行它的run方法,而上面的分析中我們知道,它的run做的事就是調(diào)用Runnable對(duì)象的run方法,如果Runnable對(duì)象為null, 就啥也不做:

@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

有的同學(xué)就要問(wèn)了,繞了一大圈,忙了大半天,最后不就是為了執(zhí)行target對(duì)象的run方法嗎?為什么我們不直接調(diào)用target的run方法?這一層層的調(diào)用究竟是為了啥? 答案是:

為了使用多線程 !

我們知道,Thread類從定義上看就是個(gè)普通的java類,是什么魔法讓它從一個(gè)普通的java類晉升為一個(gè)可以代表線程的類呢?是native方法!

如果我們直接調(diào)用target對(duì)象的run方法,或者Thread類的run方法,那就是一個(gè)普通調(diào)用,因?yàn)閞un方法就是普普通通的類方法,與我們平時(shí)調(diào)用的其他類方法沒(méi)有什么不同,這并不會(huì)產(chǎn)生多線程。

但是,如果我們調(diào)用了start方法,由于它內(nèi)部使用了native方法來(lái)啟動(dòng)線程,它將導(dǎo)致一個(gè)新的線程被創(chuàng)建出來(lái), 而我們的Thread實(shí)例, 就代表了這個(gè)新創(chuàng)建出來(lái)的線程, 并且由這個(gè)新創(chuàng)建出來(lái)的線程來(lái)執(zhí)行Thread實(shí)例的run方法。

實(shí)戰(zhàn)

說(shuō)了這么多理論的東西,下面讓我們通過(guò)一個(gè)實(shí)戰(zhàn)來(lái)加深理解。java官方文檔給我們提供了兩種創(chuàng)建線程的方法.

方法1:繼承Thread類,覆寫(xiě)run方法

首先我們自定義一個(gè)繼承自Thread的類,并覆寫(xiě)run方法:

public class CustomizedThread extends Thread {
    public void run() {
        System.out.println("[" + Thread.currentThread().getName() + "線程]: " + "我是定義在CustomizedThread類中的run方法。");
    }
}

然后我們創(chuàng)建類的實(shí)例,并調(diào)用start方法啟動(dòng)這個(gè)線程:

public class CustomizedThreadTest {
    public static void main(String[] args) {
        System.out.println("[" + Thread.currentThread().getName() + "線程]: " + "我在main方法里");
        CustomizedThread myThread = new CustomizedThread();
        myThread.start();
    }
}

執(zhí)行結(jié)果:

[main線程]: 我在main方法里
[Thread-0線程]: 我是定義在CustomizedThread類中的run方法。

可見(jiàn),這里有兩個(gè)線程,一個(gè)是main線程,它執(zhí)行了main方法,一個(gè)是Thread-0線程,它是我們自定義的線程,它執(zhí)行了run方法。

如果我們不通過(guò)start方法來(lái)運(yùn)行線程會(huì)有什么不同呢:

public class CustomizedThreadTest {
    public static void main(String[] args) {
        System.out.println("[" + Thread.currentThread().getName() + "線程]: " + "我在main方法里");
        CustomizedThread myThread = new CustomizedThread();
        //myThread.start();
        myThread.run();
    }
}

這里我們直接調(diào)用自定義線程的run方法,看看結(jié)果有什么不同:

[main線程]: 我在main方法里
[main線程]: 我是定義在CustomizedThread類中的run方法。

可見(jiàn),這次只有一個(gè)main線程,由main線程執(zhí)行了我們自定義線程類的run方法,并沒(méi)有新的線程產(chǎn)生。 其實(shí)這個(gè)時(shí)候,CustomizedThread的run方法就是一個(gè)普普通通的類的普普通通的方法,與我們平時(shí)定義的方法并沒(méi)有什么特別之處。

有的同學(xué)要問(wèn)了,上面不是說(shuō)創(chuàng)建一個(gè)線程最重要的是傳入一個(gè)Runnable對(duì)象嗎? 我沒(méi)有看到Runnable對(duì)象啊? 別急,我們來(lái)分析一下:

首先,我們的CustomizedThread繼承自Thread類,則我們會(huì)調(diào)用父類的無(wú)參構(gòu)造函數(shù):

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

這個(gè)構(gòu)造函數(shù)中,target對(duì)象為null;

然后,我們使用了myThread.start(),因?yàn)槲覀冊(cè)谧宇愔袥](méi)有定義start方法,所以,這個(gè)方法來(lái)自父類,而Thread類的start方法的作用我們已經(jīng)講過(guò),它將新建一個(gè)線程,并調(diào)用它的run方法,這個(gè)新建的線程的抽象代表就是我們的CustomizedThread,所以它的(CustomizedThread的)run方法將會(huì)被調(diào)用。

那么,如果我們的子類沒(méi)有覆寫(xiě)run方法呢?,那自然是繼承Thread類自己的run方法了:

@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

而Thread類的run方法調(diào)用的又是target對(duì)象的run方法,而target對(duì)象現(xiàn)在為null, 所以這個(gè)方法啥也不做。

所以到這里我們就很清晰了,創(chuàng)建一個(gè)線程最重要的是定義一個(gè)run方法,這個(gè)run方法要么通過(guò)繼承Thread類的子類覆寫(xiě),要么通過(guò)直接構(gòu)造Thread類時(shí)傳入一個(gè)Runnable的target對(duì)象。無(wú)論它由子類覆寫(xiě)提供還是由target對(duì)象提供,start方法最終都會(huì)新建一個(gè)線程來(lái)執(zhí)行這個(gè)run方法。

方法2:通過(guò)Runnable接口創(chuàng)建線程類

我們先來(lái)看官方的例子:

class PrimeRun implements Runnable {
     long minPrime;
     PrimeRun(long minPrime) {
         this.minPrime = minPrime;
     }

     public void run() {
         // compute primes larger than minPrime
          . . .
     }
}
//The following code would then create a thread and start it running:

 PrimeRun p = new PrimeRun(143);
 new Thread(p).start(); 

這個(gè)例子中首先定義了一個(gè)PrimeRun類實(shí)現(xiàn)了Runnable接口,接著實(shí)例化出一個(gè)對(duì)象p,并將這個(gè)對(duì)象作為參數(shù)傳遞給Thread類的構(gòu)造方法。

這種方法看上去好像復(fù)雜了好多,但其實(shí)就是通過(guò)新建Thread類的對(duì)象來(lái)創(chuàng)建線程。它本質(zhì)上就是傳遞一個(gè)Runnable對(duì)象給Thread的構(gòu)造函數(shù),所以我們完全可以用匿名類,又因?yàn)镽unnable是一個(gè)函數(shù)接口,所以上面的代碼完全可以被簡(jiǎn)寫(xiě),我們來(lái)看一個(gè)例子:

public class CustomizedThreadTest {
    public static void main(String[] args) {
        System.out.println("[" + Thread.currentThread().getName() + "線程]: " + "我在main方法里");
        Thread myThread = new Thread(() -> System.out.println("[" + Thread.currentThread().getName() + "線程]: " + "我是傳遞給Thread類的Runnable對(duì)象的run方法"));
        myThread.start();
    }
}

代碼輸出:

[main線程]: 我在main方法里
[Thread-0線程]: 我是傳遞給Thread類的Runnable對(duì)象的run方法

這里,myThread是我們new出來(lái)的Thread類的實(shí)例,我們調(diào)用了Thread類的構(gòu)造函數(shù):

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

傳入了一個(gè)Runnable對(duì)象,這個(gè)Runnable對(duì)象由lambda表達(dá)式表示。我們最后調(diào)用了 myThread.start()來(lái)啟動(dòng)這個(gè)線程,通過(guò)上一節(jié)的分析我們知道,start方法會(huì)調(diào)用run方法,而thread類的run方法最終會(huì)調(diào)用target對(duì)象的run方法,而target對(duì)象的run方法就是我們傳進(jìn)來(lái)的lambda表達(dá)式。上面這個(gè)例子其實(shí)等效于下面這種寫(xiě)法:

public class CustomizedThreadTest {
    public static void main(String[] args) {
        System.out.println("[" + Thread.currentThread().getName() + "線程]: " + "我在main方法里");
        Thread myThread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("[" + Thread.currentThread().getName() + "線程]: " + "我是傳遞給Thread類的Runnable對(duì)象的run方法");
            }
        });
        myThread.start();
    }
}

可見(jiàn)函數(shù)式接口和lambda表達(dá)式使我們的書(shū)寫(xiě)變得簡(jiǎn)潔多了。

總結(jié)

在java中,創(chuàng)建一個(gè)線程,有且僅有一種方式:

創(chuàng)建一個(gè)Thread類實(shí)例,并調(diào)用它的start方法。

創(chuàng)建一個(gè)Thread類的實(shí)例最重要的是定義一個(gè)run方法,這個(gè)run方法說(shuō)明了這個(gè)線程具體要做什么事情。有兩種方式定義一個(gè)run方法:

繼承Thread類,覆寫(xiě)run方法

實(shí)現(xiàn)Runnale接口,將它作為target參數(shù)傳遞給Thread類構(gòu)造函數(shù)

啟動(dòng)一個(gè)線程一定要調(diào)用該線程的start方法,否則,并不會(huì)創(chuàng)建出新的線程來(lái)。

(完)

查看更多系列文章:系列文章目錄

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/76743.html

相關(guān)文章

  • 系列文章目錄

    摘要:為了避免一篇文章的篇幅過(guò)長(zhǎng),于是一些比較大的主題就都分成幾篇來(lái)講了,這篇文章是筆者所有文章的目錄,將會(huì)持續(xù)更新,以給大家一個(gè)查看系列文章的入口。 前言 大家好,筆者是今年才開(kāi)始寫(xiě)博客的,寫(xiě)作的初衷主要是想記錄和分享自己的學(xué)習(xí)經(jīng)歷。因?yàn)閷?xiě)作的時(shí)候發(fā)現(xiàn),為了弄懂一個(gè)知識(shí),不得不先去了解另外一些知識(shí),這樣以來(lái),為了說(shuō)明一個(gè)問(wèn)題,就要把一系列知識(shí)都了解一遍,寫(xiě)出來(lái)的文章就特別長(zhǎng)。 為了避免一篇...

    lijy91 評(píng)論0 收藏0
  • 系列文章目錄

    摘要:為了避免一篇文章的篇幅過(guò)長(zhǎng),于是一些比較大的主題就都分成幾篇來(lái)講了,這篇文章是筆者所有文章的目錄,將會(huì)持續(xù)更新,以給大家一個(gè)查看系列文章的入口。 前言 大家好,筆者是今年才開(kāi)始寫(xiě)博客的,寫(xiě)作的初衷主要是想記錄和分享自己的學(xué)習(xí)經(jīng)歷。因?yàn)閷?xiě)作的時(shí)候發(fā)現(xiàn),為了弄懂一個(gè)知識(shí),不得不先去了解另外一些知識(shí),這樣以來(lái),為了說(shuō)明一個(gè)問(wèn)題,就要把一系列知識(shí)都了解一遍,寫(xiě)出來(lái)的文章就特別長(zhǎng)。 為了避免一篇...

    Yumenokanata 評(píng)論0 收藏0
  • Thread源碼解讀(2)——線程狀態(tài)及常用方法

    摘要:如果線程還存活,線程就無(wú)限期等待,并讓出監(jiān)視器鎖,進(jìn)入狀態(tài)。當(dāng)線程從狀態(tài)被喚醒后通過(guò),或者是假喚醒將繼續(xù)競(jìng)爭(zhēng)監(jiān)視器鎖,當(dāng)成功獲得監(jiān)視器鎖后,他將從調(diào)用的地方恢復(fù),繼續(xù)運(yùn)行。 前言 系列文章目錄 上一篇我們討論了線程的創(chuàng)建,本篇我們來(lái)聊一聊線程的狀態(tài)轉(zhuǎn)換以及常用的幾個(gè)比較重要的方法。 本篇依然是通過(guò)源碼分析來(lái)了解這些知識(shí)。 本文源碼基于jdk1.8 。 閱讀完本文,你應(yīng)當(dāng)有能力回答以...

    luqiuwen 評(píng)論0 收藏0
  • Executor線程池原理與源碼解讀

    摘要:線程池為線程生命周期的開(kāi)銷和資源不足問(wèn)題提供了解決方案。狀態(tài)說(shuō)明線程池處于狀態(tài),不接收新任務(wù),不處理已提交的任務(wù),并且會(huì)中斷正在處理的任務(wù)。線程池中允許的最大線程數(shù)。線程池的飽和策略。 線程池為線程生命周期的開(kāi)銷和資源不足問(wèn)題提供了解決方 案。通過(guò)對(duì)多個(gè)任務(wù)重用線程,線程創(chuàng)建的開(kāi)銷被分?jǐn)偟搅硕鄠€(gè)任務(wù)上。 線程實(shí)現(xiàn)方式 Thread、Runnable、Callable //實(shí)現(xiàn)Runna...

    wpw 評(píng)論0 收藏0
  • 解讀線程

    摘要:為了讓大家理解線程池的整個(gè)設(shè)計(jì)方案,我會(huì)按照的設(shè)計(jì)思路來(lái)多說(shuō)一些相關(guān)的東西。也是因?yàn)榫€程池的需要,所以才有了這個(gè)接口。 線程池是非常重要的工具,如果你要成為一個(gè)好的工程師,還是得比較好地掌握這個(gè)知識(shí)。即使你為了謀生,也要知道,這基本上是面試必問(wèn)的題目,而且面試官很容易從被面試者的回答中捕捉到被面試者的技術(shù)水平。 本文略長(zhǎng),建議在 pc 上閱讀,邊看文章邊翻源碼(Java7 和 Java...

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

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

0條評(píng)論

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