2.6 线程组
俗话说,物以类聚,人以群分。对于一组执行相同任务的线程,我们很容易想到将其划分为一组。java里使用ThreadGroup代表线程组对象,通过线程组可以同时控制一组线程的执行,进行统一异常处理逻辑等。
如上图,线程组不仅包含线程,还包含子线程组,组成一颗树的结构。每一个线程都属于一个线程组,用户创建的线程默认都在main线程组中,并且默认情况下,子线程与父线程属于一个线程组。
下面,通过源码看一下其用法:
2.6.1 字段
public class ThreadGroup implements Thread.UncaughtExceptionHandler {
// 父线程组,除了系统线程组,每个线程组都有一个父线程组
private final ThreadGroup parent;
// 线程组名称
String name;
// 限制该线程组中线程以及自线程组的最大优先级
int maxPriority;
// 是否已销毁
boolean destroyed;
// 是否后台线程组
boolean daemon;
// 是否允许VM挂起线程组中的线程,suspend()已经标记废弃,现在已经没什么用了
boolean vmAllowSuspension;
// 未启动线程数,线程只有在start()的时候才会添加到线程组中,但在初始化时
// 会通过g.addUnstarted();增加未启动线程数
int nUnstartedThreads = 0;
// 活动线程数
int nthreads;
// 该线程组中线程数组
Thread threads[];
// 子线程组数量
int ngroups;
// 子线程组
ThreadGroup groups[];
}
2.6.2 构造器
/**
* 构造系统线程组,有JVM调用
*/
private ThreadGroup() { // called from C code
this.name = "system";
this.maxPriority = Thread.MAX_PRIORITY;
this.parent = null;
}
/**
* 以当前线程所属线程组作为父线程组创建一个名为name的线程组
*/
public ThreadGroup(String name) {
this(Thread.currentThread().getThreadGroup(), name);
}
public ThreadGroup(ThreadGroup parent, String name) {
this(checkParentAccess(parent), parent, name);
}
private ThreadGroup(Void unused, ThreadGroup parent, String name) {
this.name = name;
this.maxPriority = parent.maxPriority;
this.daemon = parent.daemon;
this.vmAllowSuspension = parent.vmAllowSuspension;
this.parent = parent;
parent.add(this);
}
2.6.3 主要方法
/**
* 返回当前线程组及其自线程组中预估的活动线程数,方法采用遍历子线程组快照的方式,
* 所以,遍历时可能子线程组已经发生改变,因此,此返回值并不一定准确
*/
public int activeCount() {
int result;
// Snapshot sub-group data so we don't hold this lock
// while our children are computing.
int ngroupsSnapshot;
ThreadGroup[] groupsSnapshot;
synchronized (this) {
if (destroyed) {
return 0;
}
result = nthreads;
ngroupsSnapshot = ngroups;
if (groups != null) {
groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);
} else {
groupsSnapshot = null;
}
}
for (int i = 0 ; i < ngroupsSnapshot ; i++) {
// 可能刚调用后groupsSnapshot[i]又发生了变化,无法拿到实时信息
result += groupsSnapshot[i].activeCount();
}
return result;
}
// 可以看到,下面3个统一控制线程的方法已经被标记为废弃了,因为可能造成死锁问题
// 这个修改也降低了线程组的能力,也是许多地方不在推荐使用线程组的原因之一
@Deprecated
public final void stop();
@Deprecated
public final void suspend();
@Deprecated
public final void resume()
/**
* 中断当前线程组及其自线程组中的线程
*/
public final void interrupt() {
int ngroupsSnapshot;
ThreadGroup[] groupsSnapshot;
synchronized (this) {
checkAccess();
for (int i = 0 ; i < nthreads ; i++) {
threads[i].interrupt();
}
ngroupsSnapshot = ngroups;
if (groups != null) {
groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);
} else {
groupsSnapshot = null;
}
}
for (int i = 0 ; i < ngroupsSnapshot ; i++) {
groupsSnapshot[i].interrupt();
}
}
/**
* 在线程start的时候调用:group.add(this);
*/
void add(Thread t) {
synchronized (this) {
if (destroyed) {
throw new IllegalThreadStateException();
}
if (threads == null) {
threads = new Thread[4];
} else if (nthreads == threads.length) {
threads = Arrays.copyOf(threads, nthreads * 2);
}
threads[nthreads] = t;
// This is done last so it doesn't matter in case the
// thread is killed
nthreads++;
// 减小未启动线程数
nUnstartedThreads--;
}
}
/**
* 线程启动失败时,调用该方法回滚线程组数据
*/
void threadStartFailed(Thread t) {
synchronized(this) {
remove(t);
nUnstartedThreads++;
}
}
/**
* 线程退出时,通知线程组移除该线程,并可能销毁线程组
*/
void threadTerminated(Thread t) {
synchronized (this) {
remove(t);
if (nthreads == 0) {
notifyAll();
}
if (daemon && (nthreads == 0) &&
(nUnstartedThreads == 0) && (ngroups == 0))
{
destroy();
}
}
}
/**
* 从线程组中移除线程t,O(n)
*/
private void remove(Thread t) {
synchronized (this) {
if (destroyed) {
return;
}
for (int i = 0 ; i < nthreads ; i++) {
if (threads[i] == t) {
System.arraycopy(threads, i + 1, threads, i, --nthreads - i);
// Zap dangling reference to the dead thread so that
// the garbage collector will collect it.
threads[nthreads] = null;
break;
}
}
}
}
public int enumerate(Thread list[]) ;
public int enumerate(Thread list[], boolean recurse);
/**
* 拷贝所有活动线程值list中,n: 从list第几项开始,recurse: 是否递归子线程组
*/
private int enumerate(Thread list[], int n, boolean recurse) {
int ngroupsSnapshot = 0;
ThreadGroup[] groupsSnapshot = null;
synchronized (this) {
if (destroyed) {
return 0;
}
int nt = nthreads;
if (nt > list.length - n) {
nt = list.length - n;
}
for (int i = 0; i < nt; i++) {
if (threads[i].isAlive()) {
list[n++] = threads[i];
}
}
if (recurse) {
ngroupsSnapshot = ngroups;
if (groups != null) {
groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);
} else {
groupsSnapshot = null;
}
}
}
if (recurse) {
for (int i = 0 ; i < ngroupsSnapshot ; i++) {
n = groupsSnapshot[i].enumerate(list, n, true);
}
}
return n;
}
// 与上面相似,不过是拷贝线程组
public int enumerate(ThreadGroup list[]);
public int enumerate(ThreadGroup list[], boolean recurse);
private int enumerate(ThreadGroup list[], int n, boolean recurse);
异常处理:
/**
* 实现Thread.UncaughtExceptionHandler接口的方法,由JVM调用。
*/
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
// 1. 如果当前线程组有父线程组,调用父线程组的uncaughtException方法
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
// 2. 否则,如果有默认未捕获异常处理器,调用默认处理器处理
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
// 3. 否则,由当前线程组处理
System.err.print("Exception in thread \"" + t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
销毁线程组:
/**
* 销毁线程组及其子线程组
*/
public final void destroy() {
int ngroupsSnapshot;
ThreadGroup[] groupsSnapshot;
synchronized (this) {
checkAccess();
// 线程组必须是活动状态,并且所有线程都已停止运行
if (destroyed || (nthreads > 0)) {
throw new IllegalThreadStateException();
}
ngroupsSnapshot = ngroups;
if (groups != null) {
groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);
} else {
groupsSnapshot = null;
}
if (parent != null) {
destroyed = true;
ngroups = 0;
groups = null;
nthreads = 0;
threads = null;
}
}
// 递归销毁子线程组
for (int i = 0 ; i < ngroupsSnapshot ; i += 1) {
groupsSnapshot[i].destroy();
}
if (parent != null) {
// 从父线程组中移除
parent.remove(this);
}
}
2.6.4 实例
假设,我们开启多个线程同时抢票,当其中一个线程抢到票后或者超过一定时间,其他线程立刻退出抢票。
ThreadGroup group = new ThreadGroup("抢票线程组");
Runnable runnable = () -> {
try {
System.out.println(Thread.currentThread().getName() + ":开始抢票!");
// 休眠随机时间后,代表抢票成功
TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(5, 20));
// 将线程组中线程中断执行
Thread.currentThread().getThreadGroup().interrupt();
System.out.println(Thread.currentThread().getName() + ":抢票成功!");
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + ":取消抢票!");
}
};
for (int i = 0; i < 4; i++) {
new Thread(group, runnable, "抢票线程-" + i).start();
}
System.out.println("当前抢票线程数:" + group.activeCount());
Timer stopWatch = new Timer();
stopWatch.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("超时停止抢票。。。");
// 指定超时时间后,中断抢票线程
group.interrupt();
System.out.println("当前抢票线程数:" + group.activeCount());
group.destroy();
}
}, 10 * 1000);