线程的创建
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;// 获取创建该线程的线程,调用此方法的线程作为parentThread 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();elsethis.contextClassLoader = parent.contextClassLoader;// Java的安全检查机制this.inheritedAccessControlContext =acc != null ? acc : AccessController.getContext();// Runnable,即待运行的程序this.target = target;// 设置线程优先级setPriority(priority);// 是否从父线程那里继承ThreadLocals && 父线是否程持有inheritableThreadLocals// 如果为true,就设置inheritableThreadLocalsif (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() 的悲剧。
