thread-group-and-thread-priority.md 10.5 KB
Newer Older
沉默王二's avatar
沉默王二 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
---
category:
  - Java核心
  - 并发编程
tag:
  - Java
---

# 线程组是什么?线程优先级如何设置?

## 线程组(ThreadGroup)

Java中用ThreadGroup来表示线程组,我们可以使用线程组对线程进行批量控制。

ThreadGroup和Thread的关系就如同他们的字面意思一样简单粗暴,每个Thread必然存在于一个ThreadGroup中,Thread不能独立于ThreadGroup存在。执行main()方法线程的名字是main,如果在new Thread时没有显式指定,那么默认将父线程(当前执行new Thread的线程)线程组设置为自己的线程组。 

示例代码:

```java
public class Demo {
    public static void main(String[] args) {
        Thread testThread = new Thread(() -> {
            System.out.println("testThread当前线程组名字:" +
                    Thread.currentThread().getThreadGroup().getName());
            System.out.println("testThread线程名字:" +
                    Thread.currentThread().getName());
        });

        testThread.start();
  System.out.println("执行main所在线程的线程组名字: " + Thread.currentThread().getThreadGroup().getName());
        System.out.println("执行main方法线程名字:" + Thread.currentThread().getName());
    }
}
```

输出结果:

```java
执行main所在线程的线程组名字 main
执行main方法线程名字main
testThread当前线程组名字main
testThread线程名字Thread-0
```

ThreadGroup管理着它下面的Thread,ThreadGroup是一个标准的**向下引用**的树状结构,这样设计的原因是**防止"上级"线程被"下级"线程引用而无法有效地被GC回收**

## 线程的优先级
Java中线程优先级可以指定,范围是1~10。但是并不是所有的操作系统都支持10级优先级的划分(比如有些操作系统只支持3级划分:低,中,高),Java只是给操作系统一个优先级的**参考值**,线程最终**在操作系统的优先级**是多少还是由操作系统决定。

Java默认的线程优先级为5,线程的执行顺序由调度程序来决定,线程的优先级会在线程被调用之前设定。

通常情况下,高优先级的线程将会比低优先级的线程有**更高的几率**得到执行。我们使用方法`Thread`类的`setPriority()`实例方法来设定线程的优先级。

```java
public class Demo {
    public static void main(String[] args) {
        Thread a = new Thread();
        System.out.println("我是默认线程优先级:"+a.getPriority());
        Thread b = new Thread();
        b.setPriority(10);
        System.out.println("我是设置过的线程优先级:"+b.getPriority());
    }
}
```
输出结果:

```java
我是默认线程优先级5
我是设置过的线程优先级10
```

既然有1-10的级别来设定了线程的优先级,这时候可能有些读者会问,那么我是不是可以在业务实现的时候,采用这种方法来指定一些线程执行的先后顺序?

对于这个问题,我们的答案是:No!

Java中的优先级来说不是特别的可靠,**Java程序中对线程所设置的优先级只是给操作系统一个建议,操作系统不一定会采纳。而真正的调用顺序,是由操作系统的线程调度算法决定的**

我们通过代码来验证一下:

```java
public class Demo {
    public static class T1 extends Thread {
        @Override
        public void run() {
            super.run();
            System.out.println(String.format("当前执行的线程是:%s,优先级:%d",
                    Thread.currentThread().getName(),
                    Thread.currentThread().getPriority()));
        }
    }

    public static void main(String[] args) {
        IntStream.range(1, 10).forEach(i -> {
            Thread thread = new Thread(new T1());
            thread.setPriority(i);
            thread.start();
        });
    }
}
```

某次输出:

```java
当前执行的线程是Thread-17优先级9
当前执行的线程是Thread-1优先级1
当前执行的线程是Thread-13优先级7
当前执行的线程是Thread-11优先级6
当前执行的线程是Thread-15优先级8
当前执行的线程是Thread-7优先级4
当前执行的线程是Thread-9优先级5
当前执行的线程是Thread-3优先级2
当前执行的线程是Thread-5优先级3
```

Java提供一个**线程调度器**来监视和控制处于**RUNNABLE状态**的线程。线程的调度策略采用**抢占式**,优先级高的线程比优先级低的线程会有更大的几率优先执行。在优先级相同的情况下,按照“先到先得”的原则。每个Java程序都有一个默认的主线程,就是通过JVM启动的第一个线程main线程。

还有一种线程称为**守护线程(Daemon)**,守护线程默认的优先级比较低。

> 如果某线程是守护线程,那如果所有的非守护线程都结束了,这个守护线程也会自动结束。
>
> 应用场景是:当所有非守护线程结束时,结束其余的子线程(守护线程)自动关闭,就免去了还要继续关闭子线程的麻烦。
>
> 一个线程默认是非守护线程,可以通过Thread类的setDaemon(boolean on)来设置。

在之前,我们有谈到一个线程必然存在于一个线程组中,那么当线程和线程组的优先级不一致的时候将会怎样呢?我们用下面的案例来验证一下:

```java
public static void main(String[] args) {
    ThreadGroup threadGroup = new ThreadGroup("t1");
    threadGroup.setMaxPriority(6);
    Thread thread = new Thread(threadGroup,"thread");
    thread.setPriority(9);
    System.out.println("我是线程组的优先级"+threadGroup.getMaxPriority());
    System.out.println("我是线程的优先级"+thread.getPriority());
}
```
输出:

> 我是线程组的优先级6  
> 我是线程的优先级6

所以,如果某个线程优先级大于线程所在**线程组的最大优先级**,那么该线程的优先级将会失效,取而代之的是线程组的最大优先级。

## 线程组的常用方法及数据结构

### 线程组的常用方法

**获取当前的线程组名字**

~~~java
Thread.currentThread().getThreadGroup().getName()
~~~

**复制线程组**

~~~java
// 获取当前的线程组
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
// 复制一个线程组到一个线程数组(获取Thread信息)
Thread[] threads = new Thread[threadGroup.activeCount()];
threadGroup.enumerate(threads);
~~~

**线程组统一异常处理**

~~~java
package com.func.axc.threadgroup;
 
public class ThreadGroupDemo {
  public static void main(String[] args) {
    ThreadGroup threadGroup1 = new ThreadGroup("group1") {
            // 继承ThreadGroup并重新定义以下方法
            // 在线程成员抛出unchecked exception
            // 会执行此方法
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println(t.getName() + ": " + e.getMessage());
            }
        };
        
        // 这个线程是threadGroup1的一员
    Thread thread1 = new Thread(threadGroup1, new Runnable() {
            public void run() {
                // 抛出unchecked异常
                throw new RuntimeException("测试异常");
            }
        });
        
    thread1.start();
  }
}

~~~

### 线程组的数据结构

线程组还可以包含其他的线程组,不仅仅是线程。

首先看看 `ThreadGroup`源码中的成员变量

~~~java
public class ThreadGroup implements Thread.UncaughtExceptionHandler {
    private final ThreadGroup parent; // 父亲ThreadGroup
    String name; // ThreadGroupr 的名称
    int maxPriority; // 线程最大优先级
    boolean destroyed; // 是否被销毁
    boolean daemon; // 是否守护线程
    boolean vmAllowSuspension; // 是否可以中断
 
    int nUnstartedThreads = 0; // 还未启动的线程
    int nthreads; // ThreadGroup中线程数目
    Thread threads[]; // ThreadGroup中的线程
 
    int ngroups; // 线程组数目
    ThreadGroup groups[]; // 线程组数组
}
~~~

然后看看构造函数:

~~~java
// 私有构造函数
private ThreadGroup() { 
    this.name = "system";
    this.maxPriority = Thread.MAX_PRIORITY;
    this.parent = null;
}

// 默认是以当前ThreadGroup传入作为parent  ThreadGroup,新线程组的父线程组是目前正在运行线程的线程组。
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);
}
~~~
第三个构造函数里调用了`checkParentAccess`方法,这里看看这个方法的源码:

~~~java
// 检查parent ThreadGroup
private static Void checkParentAccess(ThreadGroup parent) {
    parent.checkAccess();
    return null;
}

// 判断当前运行的线程是否具有修改线程组的权限
public final void checkAccess() {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkAccess(this);
    }
}
~~~
> 这里涉及到`SecurityManager`这个类,它是Java的安全管理器,它允许应用程序在执行一个可能不安全或敏感的操作前确定该操作是什么,以及是否是在允许执行该操作的安全上下文中执行它。应用程序可以允许或不允许该操作。
>
> 比如引入了第三方类库,但是并不能保证它的安全性。
>
> 其实Thread类也有一个checkAccess()方法,不过是用来当前运行的线程是否有权限修改被调用的这个线程实例。(Determines if the currently running thread has permission to modify this thread.)

总结来说,线程组是一个树状的结构,每个线程组下面可以有多个线程或者线程组。线程组可以起到统一控制线程的优先级和检查线程的权限的作用。

---

>编辑:沉默王二,内容大部分来源以下三个开源仓库:
>- [深入浅出 Java 多线程](http://concurrent.redspider.group/)
>- [并发编程知识总结](https://github.com/CL0610/Java-concurrency)
>- [Java八股文](https://github.com/CoderLeixiaoshuai/java-eight-part)

<img src="http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/xingbiaogongzhonghao.png">