Java Thread解析

Java多線程基礎知識

Thread初體驗:
public class Thread extends Object implements Runnable

創建第一個線程:

文檔內容:When a Java Virtual Machine starts up, there is usually a single non-daemon thread (which typically calls the method named main of some designated class)
翻譯:當Java虛擬機啓動時,通常有一個非守護進程線程(它通常調用某個指定類的main方法)。

驗證:

public class FirstThread {
    public static void main(String[] args) {
        try {
            Thread.sleep(1000*100L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

打開cmd,使用jps查看運行的java進程pid,使用jconsole連接這個進程pid
在這裏插入圖片描述

從上圖可以看到JVM啓動時有已有多個線程存在

創建線程並啓動線程:

public class UseThread {
	public static void main(String[] args) {
        Thread thread = new Thread(UseThread::print,"Hello-Thread");
        thread.start();
    }
    private static void print() {
        System.out.println("currentThreadName:" + Thread.currentThread().getName());
    }
}
  1. start()方法:
    源碼註釋翻譯:

    1. 使該線程開始執行;Java虛擬機調用這個線程的{@code run}方法。
    2. 結果是兩個線程併發運行:當前線程(它從對{@code start}方法的調用返回)和另一個線程(它執行它的{@code run}方法)。
    3. 多次啓動一個線程是不合法的。特別是,線程在完成執行後可能不會重新啓動。
    4. 主方法線程或“系統”不調用此方法,將VM創建/設置的線程分組。將來添加到此方法中的任何新功能可能也必須添加到VM中。零狀態值對應於狀態“NEW”。
    

    執行流程:

  2. run()方法:

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

    源碼註釋翻譯:

    1. 如果這個線程是使用一個單獨的{@code Runnable} run對象構造的,那麼這個{@code Runnable}對象的{@code run}方法就會被調用;否則,此方法不執行任何操作並返回。
    2. {@code Thread}的子類應該覆蓋此方法。
    
Thread的實例化:
  1. private Thread(ThreadGroup g, Runnable target, String name, long stackSize,AccessControlContext acc,boolean inheritThreadLocals)
參數 說明
ThreadGroup g 線程組
Runnable target 調用其run()方法的對象
String name 新線程的名稱
long stackSize 新線程所需的堆棧大小,零表示忽略此參數。
AccessControlContext acc 要繼承的AccessControlContext,如果爲空,則爲AccessController.getContext()
boolean inheritThreadLocals 如果{@code true},則從構造線程繼承可繼承的線程局部變量的初始值
  1. String name分析

    public Thread() {
        this(null, null, "Thread-" + nextThreadNum(), 0);
    }
    private static int threadInitNumber;
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }
    
    由上可知Thread會提供默認的線程名:前綴:Thread- ,後綴:從0開始,每創建一個threadInitNumber數值就會加一
    
  2. Runnable target分析

    public Thread(Runnable target) {
        this(null, target, "Thread-" + nextThreadNum(), 0);
    }
    
    在0中有this.target = target;而通過run()方法我們知道如果target爲空就什麼也不做
    
  3. ThreadGroup group分析

    在0中有if (g == null) { g = parent.getThreadGroup();}那他的parent是什麼
    
    //0中
    Thread parent = currentThread();
    //返回當前執行的線程對象的引用。
    @HotSpotIntrinsicCandidate
    public static native Thread currentThread();
    
    問:誰是當前執行的線程?答:創建此線程的線程。(還是調用此線程start()方法的線程?)
    

    驗證:

    Thread thread1 = new Thread();
    Thread thread2 = new Thread(thread1::start);
    thread2.start();
    System.out.println(thread1.getThreadGroup());
    System.out.println(thread2.getThreadGroup());
    System.out.println(Thread.currentThread().getThreadGroup());
    

    輸出:

    java.lang.ThreadGroup[name=main,maxpri=10]
    java.lang.ThreadGroup[name=main,maxpri=10]
    java.lang.ThreadGroup[name=main,maxpri=10]
    

    總結:如果構造線程時沒有指定ThreadGroup,那麼子線程會默認使用父線程的ThreadGroup,此時子線程和父線程在同一個ThreadGroup中

    問:下面ThreadGroup中有多少個線程?猜測:兩個
    
    Thread thread1 = new Thread();
    thread1.start();
    Thread[] threads = new Thread[thread1.getThreadGroup().activeCount()];
    thread1.getThreadGroup().enumerate(threads);
    Arrays.asList(threads).forEach(System.out::println);
    

    輸出:

    Thread[main,5,main]
    Thread[Monitor Ctrl-Break,5,main]
    Thread[Thread-0,5,main]
    

    實際上會有三個,多了個Thread[Monitor Ctrl-Break,5,main]

  4. long stackSize分析
    jdk文檔:

    stackSize:指定線程堆棧大小。堆棧大小是虛擬機要爲此線程的堆棧分配的地址空間的大概字節數。 
    參數的作用(如果有stackSize)在很大程度上取決於平臺。在某些平臺上,stackSize參數的值 可能沒有任何作用。
    

    驗證:

    private static int count;
    public static void main(String[] args) {
    	//運行時每次調用一個,否則值被覆蓋
        testThreadStackSize("Thread1",0);
        //testThreadStackSize("Thread2",1024*1024);
    }
    public static void testThreadStackSize(String name,long stackSize) {
        new Thread(null,()-> {
            try {
                add(0);
            }catch (Error e) {
                System.out.println(Thread.currentThread().getName() + " StackDepth" + count);
            }
        },name,stackSize).start();
    }
    private static void add(int i) {
        ++ count;
        add(i+1);
    }
    

    輸出:

    Thread1 StackDepth:22564
    Thread2 StackDepth:40182
    
  5. AccessControlContext acc 分析

  6. boolean inheritThreadLocals分析

Thread的部分參數:
  1. **private boolean daemon = false;**該線程是否爲守護進程線程。
    守護線程:Java中線程分爲兩類:用戶線程和守護線程,守護線程的作用就是守護用戶線程,如果沒有可守護的人(線程)了,那他的存在就沒了意義,他會自己結束生命(線程結束,無論他自己在幹什麼),JVM退出,程序結束。

     在0中有this.daemon = parent.isDaemon();所以如果新線程沒有設置daemon ,那他默認的繼承父線程的daemon。
    

    方法:public final void setDaemon(boolean on)

     將此線程標記爲{@linkplain #isDaemon daemon}線程或用戶線程。當惟一運行的線程都是守護進程線程時,Java虛擬機退出。
     必須在線程啓動之前調用此方法。
    
  2. **private int priority;**線程的優先級,可以設置線程的優先級,但不一定管用

  3. **private final long tid;**線程的ID

    //在0中
    this.tid = nextThreadID();
    //用於生成線程ID
    private static long threadSeqNumber;
    private static synchronized long nextThreadID() {
        return ++threadSeqNumber;
    }
    
     可見線程的id值爲程序啓動後依次啓動的線程數量
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章