尽力去探究Thread的方方面面
前言
接下来的这一段时间都将探索跟线程有关的内容,包括各种锁,对Thread的总结如下:
线程具有
优先级,高优先级的线程优先于低优先级的线程执行,当在某个线程中创建新线程时,新线程的优先级被设置成当前线程的优先级;JVM启动时,默认有一个非守护线程(调用某个类的main方法);线程能被标记为守护线程,每个线程都可以指定名称,未指定的情况下将由底层帮其生成一个新名称;有两种方式来创建线程,如下所示:
// 在只有一个实例的情况下只能创建一个线程
class TestA extends Thread {
@Override
public void run() {
System.out.println("TestA");
}
}
// 在只有一个实例的情况下可创建多个线程,这种方式更为常见
class TestB implements Runnable {
@Override
public void run() {
System.out.println("TestB");
}
}
方方面面
探索Thread源码是基于JDK1.8,在探索之前先了解下线程的状态。
-
新建(NEW):新创建一个对象,但还没有开始调start方法。 -
可运行(RUNNABLE):调用start方法后,该线程处于就绪状态,简单来就是还未运行,当线程调度器把时间片分配给该线程后变成了运行中状态。 -
阻塞(BLOCKED):等待同步控制方法或代码块上的对象锁,因为该对象锁已经被其他线程所占用。当线程处于阻塞状态时,线程调度器将忽略该线程,不会分配任何CPU时间,直到该线程进入到就绪状态,它才有可能执行操作。 -
等待(WAITING):等待另外一个线程执行特定的操作,如唤醒(通知)或终止,调用Object#wait、Thread#join、LockSupport#park后会处于该状态。 -
超时等待(TIMED_WAITING):该状态与等待有些类似,只不过它是有限时间内的等待,也就是说,当另外一个线程未执行特定的操作时,经过指定的时间后该线程将会变成该状态(此时未获取到锁),当获取到锁之后就会变成可运行状态(这个状态的转变是个人猜想的,Java规范与注释都未明确说明),调用Thread#sleep(long)、Object#wait(long)、Thread#join(long)、LockSupport#parkUntil可能会处于该状态。顺便提一下,若方法中的参数都为0,那么就相当于直接调用了wait方法,此时的状态会是等待 -
终止(TERMINATED):线程执行完成,指的是从run方法返回。

简要阐述下几个重要概念,该内容摘抄自Java规范。
Object#wait、Object#notify、Object#notify只能在同步控制方法或同步代码块(synchronized)中使用,否则即使能通过编译,但在运行时会抛出IllegalMonitorStateException异常,因为这些方法在调用时会操作锁,所以它必须先获取到锁。Thread#sleep与Thread#yield可以在非同步控制方法中调用,因为它们始终没有释放锁,更不用谈操作了。
针对Object#wait(long millisecs, int nanosecs)方法,注意nanosecs参数要在0-999999范围内(毫秒与纳秒单位换算是6个0),millisecs不能为负。
我们都知道每个对象都有一个关联的监视器,即通常所说的锁,除此之外,还具有一个关联的等待容器,很容易理解,里头存放的就是处于等待状态的线程(稍微纠结了一下,阻塞状态的线程不会处于该容器中,因为它是跟监视器有关的),也就是说当前线程调用Object#wait方法后,该线程会被添加到Object的等待容器中,并释放对象锁(不管此时有多少嵌套层级的锁都会得到释放,相当于将锁的计数减到0),处于等待容器中的线程不会执行任何其他指令。而当执行notify、notifyAll、interrupt或wait超时后,该线程可能会从等待容器中删除,在获取到对象锁后,该线程会使对象重新上锁(之前是多少嵌套,现在就会有多少嵌套,相当于做了恢复),在获到CPU时间片后从而继续执行。顺便提一下,对象内部持有对锁的计数(猜测),只有当该数量变成0后其他线程才能获取该对象锁。(
理解这点概念很重要!!!否则概念多了容易乱)
notify
无法保证在等待容量中选择哪个线程作为删除,notifyAll会将所有线程从等待容量中删除。
interrupt会在对象重新上锁(什么时候重新上锁上面提到过)后引发InterruptedException异常,在try-catch块中获取到的中断状态(isInterrupted)为false,更具体的信息可看方法说明。
如果线程在等待时既被通知(notify)又被中断(interrupt),则可能是先通知后中断,也有可能是先中断后通知。
到这里应该对线程的状态有所了解了,读者可以发现实际上线程并没有运行中的状态,而在操作系统层面,它却有执行中的状态,这是为何?一个方面,CPU为每个线程分配时间片进行调度,时间片一般是几十毫秒,当其中一个线程执行一个时间片后会切换到下一个线程,在切换前会保存当前线程的状态,以便下次切换回来时可以加载该线程的状态,这样子的一个过程称为上下文切换,很显然线程之间的切换是非常快的,那么此时去区分运行中与就绪状态是没有多大意思的,有可能上一秒你看到的是运行中状态,可实际上还没等你反应过来后就变成了就绪状态,也许你只能看到这两个状态在互相闪烁着;另外一个方面,Thread注释中说:处于可运行状态下的线程正在JVM中执行,但它可能正在等待来自于操作系统的其他资源,比如处理器、CPU、各种硬件,JVM把这些都视作资源,有东西在为线程服务,它就认为线程在执行。对于操作系统来说它的侧重点是CPU,它必须要明确每个线程的具体状态,否则对于可运行状态来说,它怎么知道线程是否是可以调度的(部分观点摘自其他人的文章)。最后在提一点,JVM设置线程的状态是为了防止已经启动的线程被重新启动。
数据结构
public class Thread implements Runnable {
// 注册本地方法,比如start0、stop0,至于做了什么只能说太过于底层了,不懂C、C++的可以忽略了
private static native void registerNatives();
static {
registerNatives();
}
/**
* 线程的名称
* 为什么线程的名称要加上volatile关键词呢?
* 首先volatile保证了内存的可见性,即其中一个线程修改了某个共享变量,其他的线程能够马上看到该变量修改后的值,更多的知识点将会另起文章阐述
* 有可能多个线程共享该名称变量
*/
private volatile String name;
/**
* 线程的优先级
* 注意:对于不同的平台可能优先级不同,有的是3个优先级、有的是10个优先级,所以在设置优先级时最好写Thread.MAX_PRIORITY
*/
private int priority;
/**
* 该线程是否是守护(后台)线程
*/
private boolean daemon = false;
/**
* 线程要执行的任务,即最后会调用该任务的run方法
*/
private Runnable target;
/**
* 线程所属的线程组,一般情况下线程并未指定线程组的话默认是采用主线程的线程组,而主线程是从哪里的呢,这就要看JVM启动了...
*/
private ThreadGroup group;
/**
* 类加载器
*/
private ClassLoader contextClassLoader;
/**
* 访问控制,对访问控制进行操作或决定
* 1. 决定是否允许还是拒绝对关键系统资源的访问
* 2. 特权代码,影响访问控制决定
* 3. 可获取当前上下文,针对已保存的上下文做出来自不同上下文的访问控制决定
*/
private AccessControlContext inheritedAccessControlContext;
/**
* 当线程没有指定线程名时,内部会帮助我们生成一个新名字-Thread-number,而其中的number会随着线程的增加而递增,如Thread-1、Thread-2
*/
private static int threadInitNumber;
/**
* 每个线程都有自己的一个本地栈-ThreadLocal,ThreadLocal通过ThreadLocalMap来维护变量
* ThreadLocalMap底层维护了一个数组,数组中维护了键值对的关系,其中键是当前ThreadLocal对象,也就是说,一个ThreadLocal只能绑定一个值,若是想绑定多个值的话就要定义多个ThreadLocal对象
*/
ThreadLocal.ThreadLocalMap threadLocals = null;
/**
* 将当前线程的ThreadLocal设置到创建后的线程中,不知道使用的场景是哪里?
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
/**
* 设置栈大小,在更底层的代码中用到,该属性依赖于不同的平台会有不同的行为
*/
private long stackSize;
/**
* 可中断对象,主要用来I/O阻塞的线程调用interrupt后,需要去唤醒selector
*/
private volatile Interruptible blocker;
/**
* 未处理异常的默认处理器,即异常没有被捕获,通常情况下为null,即直接在控制台打印异常堆栈信息
* 若设置了默认处理器,则所有的线程都会应用该默认处理器,而如果又同时设置了自定义处理器,则指定线程只会应用自定义处理器
*/
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;
/**
* 异常未处理的自定义处理器,当未指定该自定义处理器后会走默认的处理器
*/
private volatile UncaughtExceptionHandler uncaughtExceptionHandler;
/**
* 记录线程ID
*/
private long tid;
/**
* 为了生成线程ID,呈现递增趋势,一开始该值并不是0,因为JVM内部也会创建系统线程
*/
private static long threadSeqNumber;
/**
* 线程的状态
* 0-NEW 1-RUNNABLE 4-RUNNABLE 2-TERMINATED 16-WAITING 32-TIMED-WAITING 1024-BLOCKED
* 其中1和4应该就是所谓的就绪与运行中,只不过哪个对应哪个就不可而知,操作系统的线程状态映射到JVM的线程状态
*/
private volatile int threadStatus = 0;
/**
* 最低优先级
*/
public final static int MIN_PRIORITY = 1;
/**
* 正常优先级
*/
public final static int NORM_PRIORITY = 5;
/**
* 最大优先级
*/
public final static int MAX_PRIORITY = 10;
}
构造函数
/**
* 初始化线程
* 自动生成线程的名称,格式:Thread-i,i是个整数
*/
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
/**
* 初始化线程
* 自动生成线程的名称
* @param target 指定任务
*/
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
/**
* 初始化线程
* 自动生成线程的名称
* @param target 指定任务
* @param acc 访问控制
*/
Thread(Runnable target, AccessControlContext acc) {
init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}
/**
* 初始化线程
* 自动生成线程的名称
* @param group 线程组
* @param target 指定任务
*/
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
/**
* 初始化线程,指定线程的名称
* @param name 线程的名称
*/
public Thread(String name) {
init(null, null, name, 0);
}
/**
* 初始化线程,指定线程的名称
* @param group 线程组
* @param name 线程的名称
*/
public Thread(ThreadGroup group, String name) {
init(group, null, name, 0);
}
/**
* 初始化线程,指定线程的名称
* @param target 指定任务
* @param name 线程的名称
*/
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
/**
* 初始化线程,指定线程的名称
* @param group 线程组
* @param target 指定任务
* @param name 线程的名称
*/
public Thread(ThreadGroup group, Runnable target, String name) {
init(group, target, name, 0);
}
/**
* 初始化线程,指定线程的名称
* stackSize依赖于不同的平台会有不同的值,所以使用该构造函数时要小心点
* @param group 线程组
* @param target 指定任务
* @param name 线程的名称
* @param stackSize 栈大小
*/
public Thread(ThreadGroup group, Runnable target, String name, long stackSize) {
init(group, target, name, stackSize);
}
简单方法
/**
* 该方法主要使用在nio的selector中,这里是存储可中断的匿名类
* 当在I/O阻塞中的线程调用interrupt时,需要去唤醒selector
* 可全局搜索该方法的调用即可知道它的使用目的
* @param b 可中断对象
*/
void blockedOn(Interruptible b) {
synchronized (blockerLock) {
blocker = b;
}
}
/**
* 获取线程名称
* @return 线程名称
*/
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
/**
* 获取线程ID
* @return 线程ID
*/
private static synchronized long nextThreadID() {
return ++threadSeqNumber;
}
/**
* 获取当前线程的引用
*/
public static native Thread currentThread();
/**
* 每个线程都有一个CPU时间片,该方法指在自愿放弃CPU时间片,让其他线程能够更快的执行
*/
public static native void yield();
/**
* 使线程进入指定时间的休眠,该方法不会释放锁,可以在非同步控制方法或同步块内使用
* @param millis 休眠指定时间,毫秒为单位
*/
public static native void sleep(long millis) throws InterruptedException;
/**
* 使线程进入指定时间的休眠,该方法不会释放锁,可以在非同步控制方法或同步块内使用
* @param millis 指定毫秒时间
* @param nanos 指定纳秒时间
*/
public static void sleep(long millis, int nanos) throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) { //纳秒与毫秒的单位换算是6个0
throw new IllegalArgumentException("nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
sleep(millis);
}
/**
* 初始化线程
* @param g 线程组
* @param target 指定任务
* @param name 指定线程名称
* @param stackSize 线程的栈大小
*/
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
init(g, target, name, stackSize, null, true);
}
/**
* 初始化线程
* @param g 线程组
* @param target 指定任务
* @param name 指定线程名称
* @param stackSize 线程的栈大小
* @param acc 访问控制,如是否允许访问系统资源
* @param inheritThreadLocals 是否将当前线程的inheritableThreadLocals设置到创建后的线程中,类似于继承
*/
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;
Thread parent = currentThread(); //获取当前线程
SecurityManager security = System.getSecurityManager();
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
g.checkAccess(); //确定当前正在运行的线程是否有权修改此线程组
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted(); //记录未启动的线程个数,即未调用start方法
this.group = g;
this.daemon = parent.isDaemon(); //若创建线程的当前线程是后台(守护)线程,则创建后的线程也是个后台线程
this.priority = parent.getPriority(); //创建后的线程会被为当前线程的优先级
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
tid = nextThreadID(); //设置线程ID
}
/**
* 启动线程,底层由JVM调用指定任务的run方法
* 该方法只能调用一次
*/
public synchronized void start() {
if (threadStatus != 0) //threadStatus表示当前线程处于NEW状态
throw new IllegalThreadStateException();
group.add(this); //将当前线程添加到线程组,至少线程组到底做了什么可能需要另外起文章来探索
boolean started = false;
try {
start0(); //启动线程
started = true;
} finally {
try {
if (!started) { //若启动失败的话,需要去线程组中将其移除掉
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
/**
* 启动线程,底层将C++的线程与Java的线程进行了绑定,至于C++中的线程又做了什么就不得而知了,能猜到应该是跟操作系统打交道了
*/
private native void start0();
/**
* 执行任务
* 若线程未调用start,直接调用run方法的话,那么就相当于执行一个方法,并未存在什么新线程
*/
public void run() {
if (target != null) {
target.run();
}
}
/**
* 由JVM调用此方法,使线程在退出之前进行资源清理
*/
private void exit() {
if (group != null) {
group.threadTerminated(this);
group = null;
}
target = null;
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
/**
* 终止线程,即使已经启动的线程也会马上被终止
*
* 1. 该方法已经被废弃了,为什么会被废弃呢?官方定义如下:
* stop本质上是不安全的. 终止一个线程会造成该线程持有的锁得到释放(当ThreadDeath异常向上传播到堆栈时,锁将被释放)
* 如果之前由这些锁保护的任何对象处于不一致状态,其他线程可能立即查看这些状态不一致的对象. 这样子的对象被认为已受损.
* 当线程操作受损对象时会导致不可预测的行为,该行为可能很难被检测到. 不像非受查异常(Exception),ThreadDeath默默地杀死了线程(简单来说,我们无法从ThreadDeath异常上得到任何的消息)
* 因此,用户没有警告其程序可能已损坏,该损坏是不可预测的.
* 举个例子:
* synchronized void f() {
* x = 1;
* x = 2;
* x = 3;
* }
* 不调用stop方法的情况下是可以正常运行的,但是在调用了stop方法后,程序立马被停止了,此时我们并不知道程序执行到了哪里,是 x = 1还是 x = 2 还是 x = 3
* 所以即使其他线程获取到锁之后也无法确定x的值,第一次执行是1,第二次执行有可能是2,这就导致了结果的不可预测,这就相当于执行中的程序在任意时刻突然被破坏了
*
*
* 2. 我不能捕捉ThreadDeath异常并修复受损的对象吗?
* 线程可以在几乎任何地方抛出ThreadDeath异常,那我们怎么知道造成了对象的受损了,那就得慢慢分析每个抛出该异常的方法了.
* 从第一个线程清除时(在catch或finally子句中),线程可以引发第二个ThreadDeath异常(最后要抛出ThreadDeath来确保线程被终止). 必须重复进行清理,直到成功.
*
* 3. 使用什么方式来代替stop?
* 通过简单地修改变量的值来表明线程应该被终止,线程应定期检查该变量,如果该变量表明要终止运行,则应有序地从其run方法返回.
*
* 方式一:
* private volatile Thread blinker;
*
* public void stop() {
* blinker = null;
* }
*
* public void run() {
* Thread thisThread = Thread.currentThread();
* while(blinker == thisThread){
* try {
* Thread.sleep(interval);
* } catch(InterruptedException e) {
*
* }
* repaint(); //业务逻辑
* }
* }
*
* 方式二:
* private final AtomicBoolean running = new AtomicBoolean(false);
* private int interval;
*
* public ControlSubThread(int sleepInterval) {
* interval = sleepInterval;
* }
*
*
* public void stop() {
* running.set(false);
* }
*
* public void run() {
* running.set(true);
* while (running.get()) {
* try {
* Thread.sleep(interval);
* } catch (InterruptedException e){
*
* }
* }
* }
*
*/
@Deprecated
public final void stop() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
checkAccess();
if (this != Thread.currentThread()) {
security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
}
}
if (threadStatus != 0) {
resume(); //唤醒挂起的线程
}
/**
* 应用程序中不能去catch ThreadDeath的实例,若真要catch,则处理完逻辑之后必须在重新抛出ThreadDeath异常对象,以便确定线程被终止
* 线程中可以自定义异常处理器,只不过该异常处理器在针对ThreadDeath时不会打印任何消息
*/
stop0(new ThreadDeath());
}
/**
* 中断线程
* 1. 若是调用wait、join、sleep方法时导致线程处于等待状态,可调用此方法来中断线程,即从这些方法处返回并抛出InterruptedException异常,同时清除中断状态
* 2. 若是调用I/O操作导致线程处于阻塞状态,可调用此方法来将通道关闭,同时会抛出一个异常,及设置中断状态
* 3. 若是调用selector.select导致线程处于阻塞状态,可调用此方法来使select立即返回,并设置中断状态
* 4. 如果上述条件均不成立,则将设置该线程的中断状态.
* 中断未启动的线程不会产生任何影响.
*/
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker; //上面咱们提到了关于blocker的作用,主要用于nio中的selector
if (b != null) {
interrupt0();
b.interrupt(this);
return;
}
}
interrupt0();// 1、2、4
}
/**
* 中断线程
*/
private native void interrupt0();
/**
* 当前线程是否被中断,该方法会清除中断状态
* 如果方法调用多次,则多次调用后会返回false,除了在多次调用后又发生中断
* @return 是否被中断
*/
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
/**
* 线程是否被中断,该方法不会清除中断状态
*/
public boolean isInterrupted() {
return isInterrupted(false);
}
/**
* 线程是否被中断,是否清除中断状态取决于ClearInterrupted参数
* @param ClearInterrupted 是否清除中断状态
*/
private native boolean isInterrupted(boolean ClearInterrupted);
/**
* 该方法原先被设计为破坏线程,并未清理资源,也就是说线程持有的锁会继续持有,并未释放锁,幸好还未实现就已经被废弃了
* 官方说若是实现了它,将与Thread#suspend方法一样容易导致死锁,锁未得到释放,其他线程无法访问
*/
@Deprecated
public void destroy() {
throw new NoSuchMethodError();
}
/**
* 线程是否处于活动状态中,活动状态表示:线程已启动(已调用start)且尚未终结
*/
public final native boolean isAlive();
/**
* 挂起/暂停线程
*
* 1. 该方法已废弃,为什么呢? 官方定义如下:
* suspend本质上是容易死锁的. 当某个线程被挂起时,其任务还持有对关键系统资源的锁,其他线程都访问不了该资源.
* 若有其他线程在调用resume之前尝试获取该锁,则会导致死锁(线程并未释放锁)
*
*
* 2. 使用什么方式来代替suspend?
*
* private volatile boolean threadSuspended;
*
* public synchronized void mousePressed() {
* //业务逻辑
*
* threadSuspended = !threadSuspended;
*
* if (!threadSuspended) {
* notify();
* }
* }
*
* public void run() {
*
* wihle(true) { //这里并未退出该任务
* try {
* Thread.sleep(interval);
*
* if(threadSuspended) { //这样子的方式可以在线程没有被挂起的情况不用花费synchronized关键词引起的代价
* synchronized(this) {
* while(threadSuspended) {
* wait();
* }
* }
* }
* } catch(InterruptedException e) {
*
* }
* }
* //业务逻辑
* }
*
*
* 3. stop与suspend结合
*
* public void run() {
* Thread thisThread = Thread.currentThread();
* while (blinker == thisThread) { //调用stop后会正常退出
* try {
* Thread.sleep(interval);
*
* synchronized(this) {
* while (threadSuspended && blinker==thisThread)
* wait();
* }
* } catch (InterruptedException e){
*
* }
* //业务逻辑
* }
* }
public synchronized void stop() {
blinker = null;
notify();
}
*
*/
@Deprecated
public final void suspend() {
checkAccess();
suspend0();
}
/**
* 挂起线程
*/
private native void suspend0();
/**
* 恢复线程
* 该方法仅与suspend一起使用,但由于suspend容易造成死锁而被废弃了,故而resume也用不到了
*/
@Deprecated
public final void resume() {
checkAccess();
resume0();
}
/**
* 设置线程的优先级
* 1 < newPriority < g.maxPriority <= 10
* @param newPriority 指定优先级
*/
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
/**
* 获取优先级
* @return 优先级
*/
public final int getPriority() {
return priority;
}
/**
* 设置线程的名称
* 即使线程已经启动了依然还是可以设置线程的名称
* @param name 指定线程的名称
*/
public final synchronized void setName(String name) {
checkAccess();
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
if (threadStatus != 0) { // threadStatus != 0 表示线程已启动
setNativeName(name);
}
}
/**
* 获取线程的名称
* @return 线程的名称
*/
public final String getName() {
return name;
}
/**
* 获取线程组
* @return 线程组
*/
public final ThreadGroup getThreadGroup() {
return group;
}
/**
* 获取当前线程组的活跃线程个数
* 注意:线程组中还有子线程组,所以计算时也会包含子线程组
* 该结果只是一个估算值,因为线程数一直在动态变化着,同时JVM也会有一些线程,所以可能会影响该计数结果
* 该方法主要用来调试与监控
* @return 线程组的活跃线程个数
*/
public static int activeCount() {
return currentThread().getThreadGroup().activeCount();
}
/**
* 将当前线程组的活跃线程拷贝到指定数组中
* 要严格确保指定数组的大小大于活跃线程的个数
* @param tarray 指定数组
* @return 线程组的活跃线程个数
*/
public static int enumerate(Thread tarray[]) {
return currentThread().getThreadGroup().enumerate(tarray);
}
/**
* 计算线程的栈帧,何为栈帧? 这就涉及到JVM的知识了,笔者还在慢慢往上走呢.
* 调用该方法时,线程应该被挂起,而suspend已被废弃了,故而该方法也没啥用了
*/
@Deprecated
public native int countStackFrames();
/**
* 当前线程最多等待指定时间
* t1.join(time);
* 这里指的当前线程并不是t1,而是执行这些所属的线程,也就是main线程,按照最上面的概念来说,应该是当前线程添加到了t1对象的等待容器中
*
* @param millis 指定毫秒时间
*/
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay); //等待状态下的线程若是调用interruput去中断的话则会直接抛出InterruptedException异常后恢复运行,若是使用notify的话,那么最终都会等待指定的millis时间才会恢复运行
now = System.currentTimeMillis() - base;
}
}
}
/**
* 当前线程最多等待指定时间
* 若指定时间设置为0,则会一直等下去
* 做了一些参数校验
* @param millis 指定毫秒时间
* @param nanos 指定纳秒数
*/
public final synchronized void join(long millis, int nanos) throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
join(millis);
}
/**
* 当前线程处于等待状态中
*/
public final void join() throws InterruptedException {
join(0);
}
/**
* 打印线程的堆栈到标准输出
* 该方法仅用于调试
*/
public static void dumpStack() {
new Exception("Stack trace").printStackTrace();
}
/**
* 根据传入的值来决定将线程变成后台线程还是用户线程
* 当所有正在运行的线程都是后台线程时,JVM退出
* 该方法必须在start之前调用
* false-用户线程 true-后台线程
* @param on 标志
*/
public final void setDaemon(boolean on) {
checkAccess();
if (isAlive()) {
throw new IllegalThreadStateException();
}
daemon = on;
}
/**
* 线程是否是后台(守护)线程
* @return 是否是后台线程
*/
public final boolean isDaemon() {
return daemon;
}
/**
* 当前线程是否有权限修改当前对象所属的线程,若没有权限则抛出异常
*/
public final void checkAccess() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkAccess(this);
}
}
/**
* 打印线程的名称,优先级,线程组
* @return 结果
*/
public String toString() {
ThreadGroup group = getThreadGroup();
if (group != null) {
return "Thread[" + getName() + "," + getPriority() + "," + group.getName() + "]";
} else {
return "Thread[" + getName() + "," + getPriority() + "," + "" + "]";
}
}
/**
* 当前线程是否持有指定对象锁
* true-表示当前线程持有指定对象锁
* @return 是否持有指定对象锁
*/
public static native boolean holdsLock(Object obj);
/**
* 获取线程的ID
* @return 线程的ID
*/
public long getId() {
return tid;
}
/**
* 获取当前线程的状态
* @return 当前线程的状态
*/
public State getState() {
return sun.misc.VM.toThreadState(threadStatus);
}
/**
* 设置未处理异常的默认处理器
* 若设置了默认处理器,则所有的线程都将应用到,相当于全局处理器,而如果同时设置了自定义处理器,那么指定线程只会应用自定义处理器(可看dispatchUncaughtException方法)
* @param eh 默认处理器
*/
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(
new RuntimePermission("setDefaultUncaughtExceptionHandler")
);
}
defaultUncaughtExceptionHandler = eh;
}
/**
* 获取未处理异常的默认处理器
* 返回null表示没有设置
* @return 默认处理器
*/
public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
return defaultUncaughtExceptionHandler;
}
/**
* 获取未处理异常的自定义处理器
* 若未设置自定义处理器,则走线程组的异常处理,线程组中会获取默认处理器,若也未设置,则打印堆栈信息到控制台,若设置了则走默认处理器
* @return 自定义处理器
*/
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler != null ? uncaughtExceptionHandler : group;
}
/**
* 设置自定义处理器
* @param eh 自定义处理器
*/
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
checkAccess();
uncaughtExceptionHandler = eh;
}
/**
* 下发异常到指定处理器上,该方法只会被JVM调用
*/
private void dispatchUncaughtException(Throwable e) {
getUncaughtExceptionHandler().uncaughtException(this, e);
}
总结
-
创建线程有
两个方式,一种是继承Thread,第二种是实现Runnable,第二种方式更为常见,且能够在只有一个任务实例的情况下创建多个线程。 -
线程的
6个状态-新建、可运行、阻塞、等待、超时等待、终止。 -
wait的实现机制是通过对象的等待容器,更具体的描述可参考概念。
-
我们都知道每个线程都有一个本地栈,实际上是通过Thread对象中的threadLocals变量维护起来的,也就是说即使将ThreadLocal变量传入到不同的线程中也不会造成多线程之间共享同一个ThreadLocal,因为每个线程都有一个ThreadLocal对象,即上面提到的threadLocals变量,在为ThreadLocal设值时,每个线程都会检查当前线程下的threadLocals变量是否初始化了,若没有则初始化该变量。有一点需要注意下,ThreadLocal中通过维护一个Map来保存键值对的关系,而其中的键值就是当前的ThreadLocal对象,也就是说,将ThreadLocal传入到不同的线程中会造成它们的threadLocals变量所引用的键值是同一个,不过这丝毫不影响。
-
sleep、yield
不会释放对象锁,可以在非同步控制方法或同步块中使用;wait、join、notify、notifyAll释放对象锁,必须在同步控制方法或同步块中使用,因为它们对锁进行了操作,故而需要先获取到锁才能操作。 -
stop:因为会造成数据的不完整,最终会导致不可预测的行为而废弃;suspend:因为始终都没有释放锁,容易造成死锁而废弃;destroy:官方说还未实现就已经废弃了,同样会造成死锁;resume:该方法仅与suspend一起使用,由于suspend已经被废弃了,那它也只好跟着牺牲了。
-
未处理异常的默认处理器与自定义处理器的使用。
重点
线程的状态转变 wait/join/notify/notify/sleep/yield
参考资料
《Java编程思想》
《Java并发编程的艺术》
浙公网安备 33010602011771号