Java多線程(二):Thread類

Thread類的實例方法

start()

start方法內部會調用方法start方法啟動一個線程,該線程返回start方法,同時Java虛擬機調用native start0啟動另一個線程調用run方法,此時有兩個線程并行執行;
我們來分析下start0方法,start0到底是如何調用run方法的

Thread類里有一個本地方法叫registerNatives,此方法註冊一些本地方法給Thread類使用
在OpenJDK官網找到Thread.c

#include "jni.h"
#include "jvm.h"

#include "java_lang_Thread.h"

#define THD "Ljava/lang/Thread;"
#define OBJ "Ljava/lang/Object;"
#define STE "Ljava/lang/StackTraceElement;"

#define ARRAY_LENGTH(a) (sizeof(a)/sizeof(a[0]))

static JNINativeMethod methods[] = {
    {"start0",           "()V",        (void *)&JVM_StartThread}, //Java中Thread類的start方法所調用的start0方法
    {"stop0",            "(" OBJ ")V", (void *)&JVM_StopThread},
    {"isAlive",          "()Z",        (void *)&JVM_IsThreadAlive},
    {"suspend0",         "()V",        (void *)&JVM_SuspendThread},
    {"resume0",          "()V",        (void *)&JVM_ResumeThread},
    {"setPriority0",     "(I)V",       (void *)&JVM_SetThreadPriority},
    {"yield",            "()V",        (void *)&JVM_Yield},
    {"sleep",            "(J)V",       (void *)&JVM_Sleep},
    {"currentThread",    "()" THD,     (void *)&JVM_CurrentThread},
    {"countStackFrames", "()I",        (void *)&JVM_CountStackFrames},
    {"interrupt0",       "()V",        (void *)&JVM_Interrupt},
    {"isInterrupted",    "(Z)Z",       (void *)&JVM_IsInterrupted},
    {"holdsLock",        "(" OBJ ")Z", (void *)&JVM_HoldsLock},
    {"getThreads",        "()[" THD,   (void *)&JVM_GetAllThreads},
    {"dumpThreads",      "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
};

......

根據關鍵字”JVM_StartThread”再找到jvm.cpp

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_StartThread");
  JavaThread *native_thread = NULL;
  bool throw_illegal_thread_state = false;

  {
    MutexLocker mu(Threads_lock);
    if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
      throw_illegal_thread_state = true;
    } else {

      jlong size =
             java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
      size_t sz = size > 0 ? (size_t) size : 0;
      native_thread = new JavaThread(&thread_entry, sz); //請看這裏,實例化了一個線程native_thread

      if (native_thread->osthread() != NULL) {
        // Note: the current thread is not being used within "prepare".
        native_thread->prepare(jthread);
      }
    }
  }

sz是大小參數,忽略之,我們看thread_entry是什麼

static void thread_entry(JavaThread* thread, TRAPS) {
  HandleMark hm(THREAD);
  Handle obj(THREAD, thread->threadObj());
  JavaValue result(T_VOID);
  JavaCalls::call_virtual(&result,
                          obj,
                          KlassHandle(THREAD, SystemDictionary::Thread_klass()),
                          vmSymbols::run_method_name(),  //請看這裏,jvm調用run_method_name方法
                          vmSymbols::void_method_signature(),
                          THREAD);
}

run_method_name在vmSymbols.hpp被定義

  /* common method and field names */                                                             
  template(run_method_name,                           "run")      //run_method_name的名稱是"run"

簡言之:當前線程調用start方法通知ThreadGroup當前線程可以運行了,可以被加入了,當前線程啟動后,當前線程狀態為”Runnable”。另一個線程等待CPU時間片,調用run方法(線程真正執行)。產生一個異步執行的效果;
用start方法來啟動線程,真正實現了多線程運行,這時無需等待run方法體代碼執行完畢而直接繼續執行下面的代碼。
代碼如下

public class MyThread03 extends Thread{
    public void run()
    {
        try
        {
            for (int i = 0; i < 3; i++)
            {
                Thread.sleep((int)(Math.random() * 1000));
                System.out.println("run = " + Thread.currentThread().getName());
            }
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }

    public static void main(String[] args)
    {
        MyThread03 mt = new MyThread03();
        mt.start();

        try
        {
            for (int i = 0; i < 3; i++)
            {
                Thread.sleep((int)(Math.random() * 1000));
                System.out.println("run = " + Thread.currentThread().getName());
            }
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

執行結果如下,可以看到,Thead-0和main線程交叉執行,是無序的。很好理解,因為main和Thread-0在爭搶CPU資源,這個過程是無序的。

run = main
run = Thread-0
run = main
run = main
run = Thread-0
run = Thread-0

再看一個例子,代碼如下

public class MyThread04 extends Thread{
    public void run()
    {
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args)
    {
        MyThread04 mt0 = new MyThread04();
        MyThread04 mt1 = new MyThread04();
        MyThread04 mt2 = new MyThread04();

        mt0.start();
        mt1.start();
        mt2.start();
    }
}

執行結果如下

Thread-0
Thread-2
Thread-1

我們依次啟動mt0,mt1,mt2,這說明線程啟動順序也是無序的。因為start方法僅僅返回調用,線程想要執行必須得到CPU時間片再執行run方法,CPU時間片的獲得是無序的。

run()

run方法是Thread類的一個普通方法,執行run方法其實是單線程執行

public class MyThread05 extends Thread{

    public void run()
    {
        System.out.println("run = " + Thread.currentThread().getName());
    }

    public static void main(String[] args)
    {
        MyThread05 mt = new MyThread05();
        mt.run();

        try
        {
            for (int i = 0; i < 3; i++)
            {
                Thread.sleep((int)(Math.random() * 1000));
                System.out.println("run = " + Thread.currentThread().getName());
            }
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

輸出結果如下

run = main
run = main
run = main
run = main

main線程循環了3次,run方法1次,結果是main線程執行了四次,我們寫在run方法體內的被main線程執行,這說明調用run方法執行多線程是不可行的。

isAlive()

判斷線程是否存活

public class MyThread06 extends Thread{
    public void run()
    {
        System.out.println("run = " + this.isAlive());
    }


    public static void main(String[] args) throws Exception
    {
        MyThread06 mt = new MyThread06();
        System.out.println("begin == " + mt.isAlive());
        mt.start();
        Thread.sleep(100);
        System.out.println("end == " + mt.isAlive());
    }
}

輸出結果如下,增加0.1秒延遲,讓線程執行完

begin == false
run = true
end == false

可以看到,執行前false,執行中true,執行后false

getId()

返回線程的標識符,線程ID是正值,線程ID在生命周期內不會變化,當線程終止了,線程ID可能會被重用

getName()

返回線程名稱

getPriority()和setPriority(int)

返回優先級和設置優先級
優先級越高的線程獲取CPU時間片的概率越高
請看如下的例子

public class MyThread07_0 extends Thread{
    public void run()
    {
        System.out.println("MyThread07_0 run priority = " +
                this.getPriority());
    }

    public static void main(String[] args)
    {
        System.out.println("main thread begin, priority = " +
                Thread.currentThread().getPriority());
        System.out.println("main thread end, priority = " +
                Thread.currentThread().getPriority());
        MyThread07_0 thread = new MyThread07_0();
        thread.start();
    }
}

運行結果如下

main thread begin, priority = 5
main thread end, priority = 5
MyThread07_0 run priority = 5

線程的默認優先級是5
再看如下的例子

public class MyThread07_1 extends Thread {

    public void run()
    {
        System.out.println("MyThread07_1 run priority = " +
                this.getPriority());
        MyThread07_0 thread = new MyThread07_0();
        thread.start();
    }

    public static void main(String[] args)
    {
        System.out.println("main thread begin, priority = " +
                Thread.currentThread().getPriority());
        System.out.println("main thread end, priority = " +
                Thread.currentThread().getPriority());
        MyThread07_1 thread = new MyThread07_1();
        thread.start();
    }
}

我們在MyThread07_1線程內部啟動MyThread07_0線程,我們觀察MyThread07_1和MyThread07_0的優先級有什麼關係。
運行結果如下

main thread begin, priority = 5
main thread end, priority = 5
MyThread07_1 run priority = 5
MyThread07_0 run priority = 5

MyThread07_0和MyThread07_1線程的優先級一致,說明線程具有繼承性。
現在我們來設置優先級

public class MyThread08 {

    static class MyThread08_0 extends Thread {
        public void run() {
            long beginTime = System.currentTimeMillis();
            for (int j = 0; j < 1000000; j++) {}
            long endTime = System.currentTimeMillis();
            System.out.println("★★★★ MyThread08_0 use time = " +
                    (endTime - beginTime));
        }
    }

    static class MyThread08_1 extends Thread {
        public void run()
        {
            long beginTime = System.currentTimeMillis();
            for (int j = 0; j < 1000000; j++){}
            long endTime = System.currentTimeMillis();
            System.out.println("☆☆☆☆ MyThread08_1 use time = " +
                    (endTime - beginTime));
        }
    }

    public static void main(String[] args)
    {
        for (int i = 0; i < 5; i++)
        {
            MyThread08_0 mt0 = new MyThread08_0();
            mt0.setPriority(5);
            mt0.start();
            MyThread08_1 mt1 = new MyThread08_1();
            mt1.setPriority(4);
            mt1.start();
        }
    }

}

我們給MyThread08_0線程設置更高的優先級5
運行結果如下

★★★★ MyThread08_0 use time = 7
☆☆☆☆ MyThread08_1 use time = 4
★★★★ MyThread08_0 use time = 18
★★★★ MyThread08_0 use time = 16
★★★★ MyThread08_0 use time = 20
★★★★ MyThread08_0 use time = 17
☆☆☆☆ MyThread08_1 use time = 0
☆☆☆☆ MyThread08_1 use time = 10
☆☆☆☆ MyThread08_1 use time = 9
☆☆☆☆ MyThread08_1 use time = 8

可以看到MyThread08_0先執行的次數更多,輸出結果為實心五角星的這個。
多運行幾次,都會是MyThread08_0先打印完,每次結果都不盡相同,CPU會盡量先讓MyThread08_0執行完。

isDaemon()和setDaemon(boolean)

isDaemon方法判斷是否是守護線程;
setDaemon設置守護線程
在Java中有兩類線程:User Thread(用戶線程)、Daemon Thread(守護線程)
我們自定義的線程和main線程都是用戶線程,我們熟知的GC(垃圾回收器)就是守護線程。守護線程是用戶線程的“奴僕”,當用戶線程執行完畢,守護線程就會終止,因為它沒有存在的必要了。
如用戶線程執行結束,GC無垃圾可回收,它只能死亡
看如下代碼

public class MyThread09 extends Thread{
    private int i = 0;

    public void run()
    {
        try
        {
            while (true)
            {
                i++;
                System.out.println(Thread.currentThread().getName()+" i = " + i);
                Thread.sleep(1000);
            }
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }

    public static void main(String[] args)
    {
        try
        {
            MyThread09 mt = new MyThread09();
            mt.setDaemon(true);
            mt.start();
            Thread.sleep(5000);
            System.out.println("現在是"+Thread.currentThread().getName()+"線程");
            Thread.sleep(1);

        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

我們自定義MyThread09線程的run方法里是死循環,如果是用戶線程,它應該永遠地執行下去,現在把它設置成守護線程。
注意:mt.setDaemon(true);要在mt.start();之前,見

否則會拋出IllegalThreadStateException異常
運行結果如下

Thread-0 i = 1
Thread-0 i = 2
Thread-0 i = 3
Thread-0 i = 4
Thread-0 i = 5
現在是main線程
Thread-0 i = 6
MyThread09變成了守護線程,它的使命已經完成。現在是main線程

Thread.sleep(5000)的目的是使main線程沉睡5s,即用戶線程(main線程)仍在執行,此時main線程輸出,再沉睡1ms,當main線程執行完畢,守護線程就沒有存在的意義了,即死亡;
main線程總共執行了大約5001ms(略大於這個數值),Thread-0打印到i=6,說明守護線程在main線程之後死亡,這個時間差極小

interrupt()

設置中斷標誌位,無法中斷線程

public class MyThread10 extends Thread{
    public void run()
    {
        for (int i = 0; i < 500000; i++)
        {
            System.out.println("i = " + (i + 1));
        }
    }

    public static void main(String[] args)
    {
        try
        {
            MyThread10 mt = new MyThread10();
            mt.start();
            Thread.sleep(2000);
            mt.interrupt();
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

輸出結果如下

......
i = 499993
i = 499994
i = 499995
i = 499996
i = 499997
i = 499998
i = 499999
i = 500000

可以看到,interrupt()沒有中斷線程,interrupt()後續將會詳細講解

isInterrupted()

判斷線程是否被中斷

join()

等待這個線程死亡,舉例說明:
線程A執行join方法,會阻塞線程B,線程A join方法執行完畢,才能執行線程B
代碼如下

public class MyThread11 extends Thread{
    public void run()
    {
        try
        {
            int secondValue = (int)(Math.random() * 1000);
            System.out.println(secondValue);
            Thread.sleep(secondValue);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception
    {
        MyThread11 mt = new MyThread11();
        mt.start();
        mt.join();
        System.out.println("MyThread11執行完畢之後我再執行");
    }
}

輸出結果如下

75
MyThread11執行完畢之後我再執行

可以看到,main線程在mt線程之後執行。mt調用join方法,使main線程阻塞,待mt線程執行完畢,方可執行main線程。

Thread類的靜態方法

currentThread()

返回當前正在執行線程的引用

public class MyThread12 extends Thread{

    static
    {
        System.out.println("靜態塊的打印:" +
                Thread.currentThread().getName());
    }

    public MyThread12()
    {
        System.out.println("構造方法的打印:" +
                Thread.currentThread().getName());
    }

    public void run()
    {
        System.out.println("run()方法的打印:" +
                Thread.currentThread().getName());
    }



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


}

輸出結果

靜態塊的打印:main
構造方法的打印:main
run()方法的打印:Thread-0

可以看到,構造方法和靜態塊是main線程在調用,重寫的run方法是線程自己在調用。
再看個例子

public class MyThread13 extends Thread{
    public MyThread13()
    {
        System.out.println("MyThread13----->Begin");
        System.out.println("Thread.currentThread().getName()----->" +
                Thread.currentThread().getName());
        System.out.println("this.getName()----->" + this.getName());
        System.out.println("MyThread13----->end");
    }

    public void run()
    {
        System.out.println("run----->Begin");
        System.out.println("Thread.currentThread().getName()----->" +
                Thread.currentThread().getName());
        System.out.println("this.getName()----->" + this.getName());
        System.out.println("run----->end");
    }



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


}

輸出結果

MyThread13----->Begin
Thread.currentThread().getName()----->main
this.getName()----->Thread-0
MyThread13----->end
run----->Begin
Thread.currentThread().getName()----->Thread-0
this.getName()----->Thread-0
run----->end

可以看到,執行MyThread13構造方法的線程是main,執行MyThread13的線程是Thread-0(當前線程),run方法就是被線程實例所執行。

sleep(long)

讓當前線程沉睡若干毫秒

public class MyThread14 extends Thread{
    public void run()
    {
        try
        {
            System.out.println("run threadName = " +
                    this.getName() + " begin");
            Thread.sleep(2000);
            System.out.println("run threadName = " +
                    this.getName() + " end");
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }

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

輸出結果如下

run threadName = Thread-0 begin
run threadName = Thread-0 end

打印完第一句兩秒后打印第二句。

yield()

當前線程放棄CPU的使用權,這裏的放棄是指當前線程少用CPU資源,最後線程還是會執行完成

public class MyThread15 extends Thread {
    public void run()
    {
        long beginTime = System.currentTimeMillis();
        int count = 0;
        for (int i = 0; i < 5000000; i++)
        {
            Thread.yield();
            count = count + i + 1;
        }
        long endTime = System.currentTimeMillis();
        System.out.println("用時:" + (endTime - beginTime) + "毫秒!");
    }



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


}

輸出結果如下

用時:4210毫秒!

可以看到,任務執行完畢,當我們把Thread.yield();註釋掉,執行時間只需要7ms。說明當前線程放棄了一些CPU資源。

interrupted()

判斷當前線程是否中斷,靜態版的isInterrupted方法。多線程中斷機制,後續會詳細解析。

【精選推薦文章】

如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

想要讓你的商品在網路上成為最夯、最多人討論的話題?

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師"嚨底家"!!