java多线程之细说线程组

518 阅读5分钟

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);