大家好我是kconn,我是一个不爱看源码,不喜欢分析源码,更不喜欢写文章的程序员。自从面试被人虐后,我改过自新,打算重新做人。不是,是重新做猿。啊呸,是好好学习,天天肛源码。
前段时候看Thread的时候,有点想写篇关于他的文章,但是发现里面的内容有点多,于是就打算拆出好几份来写。今天我要肛的对象就是ThreadGroup,翻译过来就是“线程组”,也就是线程的老管家。
好,废话不多说,我们来肛源码!
点开ThreadGroup类。
// 系统线程组
static final ThreadGroup systemThreadGroup = new ThreadGroup();
// 主线程组
static final ThreadGroup mainThreadGroup = new ThreadGroup(systemThreadGroup, "main");
// 父线程组
private final ThreadGroup parent;
// 线程组里面的线程组数组,也就是子线程组数组
ThreadGroup groups[];
// 线程数组
Thread threads[];
...
/**
* 系统线程组的专属
*/
private ThreadGroup() {
this.name = "system";
// 优先级,内定的最强王者
this.maxPriority = Thread.MAX_PRIORITY;
// 系统就是最大的了,所以父线程组为null
this.parent = null;
}
public ThreadGroup(String name) {
// 太吾绘卷核心思想——薪火相传
this(Thread.currentThread().getThreadGroup(), name);
}
public ThreadGroup(ThreadGroup parent, String name) {
// checkParentAccess按资料显示是判定当前运行的线程是否有权修改该线程
this(checkParentAccess(parent), parent, name);
}
/**
* 这里要说下Void是void的包装类,和Integer是int的包装类一样。
*/
private ThreadGroup(Void unused, ThreadGroup parent, String name) {
// 线程组名
this.name = name;
// 优先级
this.maxPriority = parent.maxPriority;
// 是否是守护线程或用户线程
this.daemon = parent.daemon;
// boolean类型,没参与任何判断,没看出有什么作用。资料显示是否可中断
this.vmAllowSuspension = parent.vmAllowSuspension;
this.parent = parent;
// 子线程组加入父线程组
parent.add(this);
}
好了,饭要一口一口的吃,先让我们来看下checkParentAccess是怎么判断的
private static Void checkParentAccess(ThreadGroup parent) {
parent.checkAccess();
return null;
}
public final void checkAccess() {
// 里面真的什么都没有
}
checkAccess是个空方法,我瞬间懵逼了,翻了下前面几个版本的源码,里面都是空空如也。不知道这么做的意义是什么。为了父线程组为空的时候抛异常?也就是说,只要他不为空就有权修改该线程?好吧,这问题就放下不表。
好了,接着看add方法。
private final void add(ThreadGroup g){
synchronized (this) {
// 如果该线程组销毁了就抛异常,没毛病
if (destroyed) {
throw new IllegalThreadStateException();
}
// 为空就创建
// 不为空并且超过了就扩容
if (groups == null) {
groups = new ThreadGroup[4];
} else if (ngroups == groups.length) {
groups = Arrays.copyOf(groups, ngroups * 2);
}
// 子线程组加入,成为马仔
groups[ngroups] = g;
ngroups++;
}
}
好了,以上就是本篇的全部内容。
咳咳,不皮了,我们刚刚看完了他的构造方法,接下来看看在Thread里面他是怎么运用的。首先先找Thread里使用的场景。
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
// 如果线程组为空就采用当前线程的线程组
Thread parent = currentThread();
if (g == null) {
g = parent.getThreadGroup();
}
// 未开始执行的线程数+1
g.addUnstarted();
...
}
接着点进去看addUnstarted方法。
void addUnstarted() {
synchronized(this) {
if (destroyed) {
throw new IllegalThreadStateException();
}
// 未开始执行的线程数+1
nUnstartedThreads++;
}
}
也就是在初始化Thread的时候,未开始执行的线程+1,这也很好理解,我们在Thread没start的时候也就代表这个线程还未开始执行。
public synchronized void start() {
...
// 线程组加入线程
group.add(this);
started = false;
try {
...
} finally {
try {
if (!started) {
// 从线程中移除当前线程,未启动线程数+1
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
这里要说明下,线程组里面有两个add方法,一个是add子线程组,另一个就是add线程。
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;
nthreads++;
// 未启动线程数-1
nUnstartedThreads--;
}
}
仔细的我发现,add线程组的方法是private的,而add线程的方法则是默认的friendly。nUnstartedThreads-1也代表了Thread开始的时候,那么代表这个线程开始执行了。
好吧,接着往下看。
/**
* 线程启动失败
*/
void threadStartFailed(Thread t) {
synchronized(this) {
// 移除线程
remove(t);
// 未启动线程数+1
nUnstartedThreads++;
}
}
看下remove方法。
private void remove(Thread t) {
synchronized (this) {
// 如果已经销毁,不执行后面代码
if (destroyed) {
return;
}
for (int i = 0 ; i < nthreads ; i++) {
if (threads[i] == t) {
// copy数组
System.arraycopy(threads, i + 1, threads, i, --nthreads - i);
// 干掉线程
threads[nthreads] = null;
break;
}
}
}
}
好了,Thread的start场景都看过了,现在回过头看Thread的exit方法。
private void exit() {
if (group != null) {
// 移除线程
group.threadTerminated(this);
// 老管家退休
group = null;
}
...
}
进入到ThreadGroup的threadTerminated方法。
void threadTerminated(Thread t) {
synchronized (this) {
// 移除线程
remove(t);
if (nthreads == 0) {
notifyAll();
}
// 满足以下条件就销毁
if (daemon && (nthreads == 0) &&
(nUnstartedThreads == 0) && (ngroups == 0))
{
destroy();
}
}
}
destroy方法也没啥好说的,看下就行了。
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);
}
}
前面看着有两个add,这里也发现有两个remove。
private void remove(ThreadGroup g) {
synchronized (this) {
// 移除的逻辑都差不多
if (destroyed) {
return;
}
for (int i = 0 ; i < ngroups ; i++) {
if (groups[i] == g) {
ngroups -= 1;
System.arraycopy(groups, i + 1, groups, i, ngroups - i);
groups[ngroups] = null;
break;
}
}
if (nthreads == 0) {
notifyAll();
}
if (daemon && (nthreads == 0) &&
(nUnstartedThreads == 0) && (ngroups == 0))
{
destroy();
}
}
}
以上就是Thread运用到ThreadGroup的场景做一次比较简单的源码阅读。
最后再说一句,肛源码不容易,我也不是大牛,文章也写的不是很好。大家看着有什么想法都可以说,我不一定会看,看了也不一定会回,因为我懒!
bye~