线程的创建
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
// 必定要传一个线程名字
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
// 获取创建该线程的线程,调用此方法的线程作为parent
Thread parent = currentThread();
// 安全检查
...
// 记录为启动的线程数量
g.addUnstarted();
// 设置线程组
this.group = g;
// 设置是否为守护线程
this.daemon = parent.isDaemon();
// 保存线程优先级
this.priority = parent.getPriority();
// isCCLOverridden()是Java的安全检查机制
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
// Java的安全检查机制
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
// Runnable,即待运行的程序
this.target = target;
// 设置线程优先级
setPriority(priority);
// 是否从父线程那里继承ThreadLocals && 父线是否程持有inheritableThreadLocals
// 如果为true,就设置inheritableThreadLocals
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
// 设置栈大小
this.stackSize = stackSize;
// 设置线程ID(单纯的递增了一下)
tid = nextThreadID();
}
这里有几个点可以着重关注一下:
- 线程的
parent
是创建该线程的线程 - 父线程可以通过
inheritableThreadLocals
将ThreadLocal
共享给子线程。简单来说,ThreadLocal
是作为Key
绑定到当前线程的,如果有一些必要的值想要传给子线程,通常要暴力传入使用了。但是通过InheritableThreadLocal
可以很自然的将要继承的值在创建子线程时设置给子线程
Tips 在使用线程的时候,最好给每个线程都标注好名称,方便到时候根据日志排错时能更方便地定位到异常!
线程启动
当调用 Thread#start()
时,线程就被创建出来了。本质是让 JVM
创建一个操作系统级的线程,然后映射到虚拟机线程上~
具体的,等我学玩操作系统,看完 JVM
源码,过来补充~
线程中止
个人觉得 Java
最正统的结束就是让其执行完毕,正常结束,想要达到这个目的一般有两种手段:
- 中断(
interrupt
) - 设置标志位
这两者本质上是一样的,都是借助于某一个标识位来决定程序的进程走向。如果一定要说区别,第一种方式是借用 Thread
内置的中断标识位, Thread
本身提供了一些处理机制;第二种主要依赖共享对象的线程间通信来完成的。
中断
中断是一个动作,即设置线程自带的一个标识位,线程会检查自身是否被中断来响应这个标识位。目前和中断有关的方法有下面三个:
方法名 | 作用 |
---|---|
ThreadObj.interrupt() | 设置指定线程的中断状态 |
ThreadObj.isInterrupted() | 判断当前线程是否被中断。若线程已结束但处于中断状态,依然返回false; |
Thread.interrupted() | 对当前所在线程的中断标识位进行复位 |
当我们对可以抛出InterruptedException
异常的方法执行中断时,这些方法在抛出 InterruptedException
之前, JVM
会清除该线程的中断标识位,然后抛出 InterruptedException
,此时 ThreadObj.isInterrupted()
会返回 FALSE
。
suspend()、resume()、stop()这仨方法已经废弃了~
- suspend():在调用该方法后,线程不会释放已经占有的资源(比如锁),而是持有着锁进入睡眠状态,这样就容易发生死锁;
- stop():在调用该方法后,不会保证线程的资源正确释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下;
这部分样例、细节可以看《Java并发 - 为什么Thread#suspend被弃用?》
设置标志位
public static void main(String[] args) throws Exception {
// ===================第一种方式=======================
Runner one = new Runner();
Thread countThread = new Thread(one, "CountThread");
countThread.start();
// 睡眠1秒,main线程对CountThread进行中断,使CountThread能够感知中断而结束
TimeUnit.SECONDS.sleep(1);
countThread.interrupt();
// ===================第二种方式=======================
Runner two = new Runner();
countThread = new Thread(two, "CountThread");
countThread.start();
// 睡眠1秒,main线程对Runner two进行取消,使CountThread能够感知on为false而结束
TimeUnit.SECONDS.sleep(1);
two.cancel();
}
private static class Runner implements Runnable {
private long i;
private volatile boolean on = true;
@Override
public void run() {
while (on && !Thread.currentThread().isInterrupted()){
i++;
}
System.out.println("Count i = " + i);
}
public void cancel() {
on = false;
}
}
main
线程可以通过中断操作和 cancel()
方法使得 CountThread
线程终止。通过中断、设置标志位这样的方式可以让线程结束前有机会清理资源,将资源释放工作交给开发人员,避免 Thread#stop()
的悲剧。