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();
}
}
}
從上圖可以看到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());
}
}
-
start()方法:
源碼註釋翻譯:1. 使該線程開始執行;Java虛擬機調用這個線程的{@code run}方法。 2. 結果是兩個線程併發運行:當前線程(它從對{@code start}方法的調用返回)和另一個線程(它執行它的{@code run}方法)。 3. 多次啓動一個線程是不合法的。特別是,線程在完成執行後可能不會重新啓動。 4. 主方法線程或“系統”不調用此方法,將VM創建/設置的線程分組。將來添加到此方法中的任何新功能可能也必須添加到VM中。零狀態值對應於狀態“NEW”。
執行流程:
-
run()方法:
public void run() { if (target != null) {target.run();} }
源碼註釋翻譯:
1. 如果這個線程是使用一個單獨的{@code Runnable} run對象構造的,那麼這個{@code Runnable}對象的{@code run}方法就會被調用;否則,此方法不執行任何操作並返回。 2. {@code Thread}的子類應該覆蓋此方法。
Thread的實例化:
- 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},則從構造線程繼承可繼承的線程局部變量的初始值 |
-
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數值就會加一
-
Runnable target分析
public Thread(Runnable target) { this(null, target, "Thread-" + nextThreadNum(), 0); }
在0中有this.target = target;而通過run()方法我們知道如果target爲空就什麼也不做
-
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]
-
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
-
AccessControlContext acc 分析
-
boolean inheritThreadLocals分析
Thread的部分參數:
-
**private boolean daemon = false;**該線程是否爲守護進程線程。
守護線程:Java中線程分爲兩類:用戶線程和守護線程,守護線程的作用就是守護用戶線程,如果沒有可守護的人(線程)了,那他的存在就沒了意義,他會自己結束生命(線程結束,無論他自己在幹什麼),JVM退出,程序結束。在0中有this.daemon = parent.isDaemon();所以如果新線程沒有設置daemon ,那他默認的繼承父線程的daemon。
方法:public final void setDaemon(boolean on)
將此線程標記爲{@linkplain #isDaemon daemon}線程或用戶線程。當惟一運行的線程都是守護進程線程時,Java虛擬機退出。 必須在線程啓動之前調用此方法。
-
**private int priority;**線程的優先級,可以設置線程的優先級,但不一定管用
-
**private final long tid;**線程的ID
//在0中 this.tid = nextThreadID(); //用於生成線程ID private static long threadSeqNumber; private static synchronized long nextThreadID() { return ++threadSeqNumber; }
可見線程的id值爲程序啓動後依次啓動的線程數量