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值为程序启动后依次启动的线程数量