线程组
为了便于对某些具有相同功能的线程进行管理,可以把线程归属到某一个线程组中,线程组中既可以有线程对象,也可以有线程组,组中也可以有线程;
public class Test6 {
public static void main(String[] args) throws InterruptedException {
//自定义线程组挂在main线程组下
//ThreadGroup mainGroup = Thread.currentThread().getThreadGroup(); 取得main主线程所在的线程组
//ThreadGroup group = new ThreadGroup(mainGroup,"自定义线程组");
ThreadGroup group = new ThreadGroup("自定义线程组"); //不指定所属的线程组则自动归到当前线程对象所属的线程组中
new Thread(group,()-> System.out.println("---")).start();
new Thread(group,()-> System.out.println("+++")).start();
System.out.println(group.activeCount()); //打印2,线程组中活动的线程数量
System.out.println(group.activeGroupCount()); //打印1,取得当前线程组对象中的子线程组数量
System.out.println(group.getName()); //线程组的名称
}
}
根线程组
System.out.println(Thread.currentThread().getThreadGroup().getParent().getName()); //打印system
System.out.println(Thread.currentThread().getThreadGroup().getParent().getParent().getName()); //报空指针异常
结果说明JVM的根线程组是system,再取父线程组则出现空异常;
批量停止线程组中的线程
通过将线程归属到线程组中,当调用线程组ThreadGroup的interrupt()方法时可以中断该组中所有正在运行的线程:
public class Test6 implements Runnable{
public static void main(String[] args) throws InterruptedException {
ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
ThreadGroup group = new ThreadGroup(mainGroup,"自定义线程组");
Test6 test6 = new Test6();
for (int i = 0; i < 10; i++) {
new Thread(group,test6).start();
}
Thread.sleep(100);
group.interrupt();
}
@Override
public void run() {
for (int i = 0; i < 1000000; i++) {
if (Thread.currentThread().isInterrupted()){
System.out.println(Thread.currentThread().getName()+"已被中断");
break;
}else {
System.out.println(Thread.currentThread().getName()+","+i);
}
}
}
}
实现线程组中一个线程异常停止所有线程
class MyThreadGroup extends ThreadGroup{
public MyThreadGroup(String name) {
super(name);
}
public MyThreadGroup(ThreadGroup parent, String name) {
super(parent, name);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
super.uncaughtException(t, e);
this.interrupt();
}
}
public class Test6 implements Runnable {
private String s;
public Test6(String s) {
this.s = s;
}
@Override
public void run() {
int num = Integer.parseInt(s);
while (!Thread.currentThread().isInterrupted()){
System.out.println(Thread.currentThread().getName()+"执行任务中");
}
}
public static void main(String[] args){
MyThreadGroup threadGroup = new MyThreadGroup("我的线程组");
Test6 test6 = new Test6("2");
for (int i = 0; i < 4; i++) {
new Thread(threadGroup,test6).start();
}
new Thread(threadGroup,new Test6("a")).start();
}
}
需要注意的是,使用自定义的线程组并重写uncaughtException()方法处理组内线程中断行为时,每个线程对象中的run()方法内部不要有异常catch语句,如果有catch语句,则public void uncaughtException(Thread t, Throwable e)方法不会执行!
线程池
线程池就是创建若干个可执行的线程放入一个池(容器)中,有任务需要处理时,会提交到线程池中的任务队列,处理完之后线程并不会被销毁,而是仍然在线程池中等待下一个任务;
为什么要使用线程池?
因为 Java 中创建一个线程,需要调用操作系统内核的 API,操作系统要为线程分配一系列的资源,成本很高,所以线程是一个重量级的对象,应该避免频繁创建和销毁。使用线程池就能很好地避免频繁创建和销毁!
线程池构造函数参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- keepAliveTime:保持存活时间
- unit:存活时间单位,秒、分钟、小时、天等等
- workQueue:任务存储队列
- threadFactory:当线程池需要新的线程的时候,会使用threadFactory来生成新的线程
- handler:线程池无法接受新提交的任务的拒绝策略
核心线程数就像是工厂正式工,非核心线程数则类似工厂临时工;
生活中小例子:当工厂工作量突然加大时,正式工工人已经无法完成这么大的工作量的时候,工厂会再外招一批临时工,临时工加正式工的和就是最大线程数,等这批任务结束后,临时工是要辞退的(存活时间就是keepAliveTime设置的值),而正式工则会一直留下;
任务队列有如下3种常见类型:
- SynchronousQueue:它的容量是0(即SynchronousQueue不存储任何元素),每个 put 必须等待一个 take,反之亦然;
- LinkedBlockingQueue:无界队列;
- ArrayBlockingQueue:有界队列;
无界队列:没有设置固定大小的队列。这些队列的特点是可以直接入列,直到溢出(超过 Integer.MAX_VALUE),相当于无界;
有界队列:有设置固定大小的队列;
拒绝策略:
- AbortPolicy:直接抛出RejectedExecutionException异常
- DiscardPolicy:丢弃任务
- DiscardOldestPolicy:丢弃旧任务,以便腾出空间保存新提交的任务
- CallerRunsPolicy:如果线程池已经饱和,则谁提交的任务谁去跑,比如主线程提交的任务,这个时候就会交给主线程去执行;
使用线程池
1、newFixedThreadPool()
拥有固定线程数的线程池,不管提交的任务多还是少,都创建固定数量的线程;
public class Test7
{
public static void main(String[] args){
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
executorService.execute(new Task());
}
}
static class Task implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
}
运行程序,查看控制台打印语句我们发现就算我们提交了100个任务,线程池也只会创建固定的10个线程来执行任务;
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
查看源码,我们知道这个线程池是将核心线程数与最大线程数设置为一样;
由于LinkedBlockingQueue是无界队列,没有容量上限。所以当请求数越来越多,并且无法及时处理完毕的时候,也就是请求堆积的时候,会容易造成占用大量的内存,可能会导致OOM;
2、newSingleThreadExecutor()
只有一个线程的线程池,固定创建一个线程;
public class Test7
{
public static void main(String[] args){
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
executorService.execute(new Task());
}
}
static class Task implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
}
运行程序,查看控制台打印语句我们发现创建的线程都是同一个;
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
查看源码,我们知道这个线程池是将核心线程数与最大线程数都设置为1;
和newFixedThreadPool一样,这个也会导致同样的问题,也就是当请求堆积的时候,可能会占用大量的内存。
3、newCachedThreadPool()
可缓存线程池;
ExecutorService executorService = Executors.newCachedThreadPool();
如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程;当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
查看源码,我们发现线程池为无限大,这也可能会出现占用大量的内存导致OOM的问题;
4、newScheduledThreadPool()
支持定时及周期性任务执行的线程池;
public class Test7
{
public static void main(String[] args){
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
//
scheduledThreadPool.schedule(new Task(),3L,TimeUnit.SECONDS);
scheduledThreadPool.scheduleAtFixedRate(new Task(),1,3,TimeUnit.SECONDS);
}
static class Task implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
}
5、ThreadPoolExecutor()
由于jdk中Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活【消耗内存等】;另外由于前面几种方法内部也是通过ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险,所以我们使用线程池应首选ThreadPoolExcutor;
ThreadPoolExecutor threadPool= new ThreadPoolExecutor(2, 4, 3,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),
new ThreadPoolExecutor.DiscardOldestPolicy());
线程池状态
ThreadPoolExecutor 使用 runState变量对线程池的生命周期进行控制,有以下5种:
- RUNNING:接收新的任务并对任务队列里的任务进行处理;
- SHUTDOWN:不再接收新的任务,但是会对任务队列中的任务进行处理;
- STOP:不接收新任务,也不再对任务队列中的任务进行处理,并中断正在处理的任务;
- TIDYING:所有任务都已终止,线程数为0,在转向TIDYING状态的过程中,线程会执行terminated()钩子方法,钩子方法是指在本类中是空方法,而在子类中进行具体实现的方法;
- TERMINATED:terminated()方法执行结束后会进入这一状态,表示线程池已关闭;
运行状态的转化条件和转化关系如下所示:
线程池核心方法
1、shutdown()
当线程池调用该方法时,线程池的状态则立刻变成SHUTDOWN状态。此时,则不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。
public class Test7
{
public static void main(String[] args){
ExecutorService threadPool = Executors.newFixedThreadPool(10);
threadPool.execute(new Task());
threadPool.shutdown();
threadPool.execute(new Task());
}
static class Task implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
}
运行程序,将会报以下异常:
2、isShutdown()
isShutDown当调用shutdown()或shutdownNow()方法后返回为true;
3、isTerminated()
如果关闭后所有任务都已完成(包括正在执行的任务以及任务队列里的任务),则返回 true;
4、awaitTermination()
当前线程阻塞,直到
- 所有已提交的任务(包括正在跑的和队列中等待的)执行完
- 超时时间到
- 线程被中断,抛出InterruptedException
然后返回true(shutdown请求后所有任务执行完毕)或false(已超时);
5、shutdownNow()
执行该方法,线程池的状态立刻变成STOP状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,当然,它会返回那些未执行的任务;
ThreadPoolTest.java
//实现Runnable接口来定义一个简单的
class TestThread implements Runnable
{
public void run()
{
for (int i = 0; i < 100 ; i++ )
{
System.out.println(Thread.currentThread().getName()
+ "的i值为:" + i);
}
}
}
public class ThreadPoolTest
{
public static void main(String[] args)
{
//创建一个具有固定线程数(6)的线程池
ExecutorService pool = Executors.newFixedThreadPool(6);
//向线程池中提交2个线程
pool.submit(new TestThread());
pool.submit(new TestThread());
//关闭线程池
pool.shutdown();
}
}
ThreadLocal
ThreadLocal解决的是变量在不同线程的隔离性,也就是不同线程拥有自己的值,不同线程的值是通过ThreadLocal类进行保存的!
public class Test6{
public static void main(String[] args) throws InterruptedException {
ThreadLocal local = new ThreadLocal();
local.set("线程:"+Thread.currentThread().getName()+",123");
System.out.println(local.get());
new Thread(()->{
local.set("线程:"+Thread.currentThread().getName()+",456");
System.out.println(local.get());
}).start();
}
//main线程创建了此子线程,但是main线程中的值并没有继承给子线程
new Thread(()->{System.out.println(local.get());}).start();
}
//控制台打印
线程:main,123
线程:Thread-0,456
null
结果表明ThreadLocal可向每个线程存储自己的私有数据!类ThreadLocal不能实现值继承
ThreadLocal存流程分析
set源代码如下:
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
取得Thread中的ThreadLocal.ThreadLocalMap后,第一次向其存放数据时会调用createMap()方法来创建ThreadLocal.ThreadLocalMap对象,createMap()源码如下:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
key是当前ThreadLocal对象,值就是传入的value。然后被封装进Entry对象中,并放入table[]数组中。
ThreadLocal取流程分析
get源代码如下:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
最终从Entry对象中取得数据!
initialValue()
在第一次调用ThreadLocal类的get()方法时,返回值是Null。怎样使其具有默认值的效果呢?
覆盖initialValue()方法具有初始值,因为ThreadLocal中的initialValue()方法默认返回值是null,需要在子类中进行重写:
public class Test6 extends ThreadLocal{
@Override
protected Object initialValue() {
return "默认值-"+System.currentTimeMillis();
}
public static void main(String[] args) throws InterruptedException {
Test6 test6 = new Test6();
System.out.println(test6.get());
new Thread(()->{System.out.println(test6.get());}).start();
}
}
//控制台打印
默认值-1641889927195
默认值-1641889927262
结果表明子线程和父线程各拥有自己所拥有的值!
InheritableThreadLocal
使用类InheritableThreadLocal可使子线程继承父线程的值:
public class Test6{
public static void main(String[] args) throws InterruptedException {
InheritableThreadLocal threadLocal = new InheritableThreadLocal();
threadLocal.set("123");
System.out.println(threadLocal.get());
new Thread(()->{System.out.println(threadLocal.get());}).start();
}
}
//控制台打印
123
123
值继承特性执行流程分析
InheritableThreadLocal源码如下:
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
这三个方法都是对父类ThreadLocal中的同名方法进行重写。
执行InheritableThreadLocal对象中的set()方法其实就是调用ThreadLocal中的set()方法,因为此方法InheritableThreadLocal并没有进行重写。但是getMap(Thread t)与createMap(Thread t, T firstValue)被重写了;
由源码可知,重写后的方法不再向Thread中的ThreadLocal.ThreadLocalMap threadLocals存入数据了,而是向ThreadLocal.ThreadLocalMap inheritableThreadLocals 存入数据,那么子线程如何实现从父线程中的inheritableThreadLocals 对象继承值呢?
这个实现思路就是在创建线程ThreadA时,子线程主动引用父线程main中的inheritableThreadLocals 对象值,源码如下:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
//父线程的不为null,则对当前线程的inheritableThreadLocals对象变量进行赋值
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
最后一个参数inheritThreadLocals代表当前线程对象是否会从父线程继承值!
接着查看ThreadLocalMap源码:
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
发现子线程将父线程中的table对象以复制的方式赋值给子线程的table数组,这个过程是在创建Thread类对象时发生的,也就是说明当子线程对象创建完毕后,子线程中的数据就是主线程中旧的数据,主线程使用新的数据时,子线程还是使用旧的数据,因为主子 线程使用两个Entry[]对象数组各自存储自己的值!
示例1:改了父线程的值,子线程还是旧值
public class Test6{
public static void main(String[] args) throws InterruptedException {
InheritableThreadLocal local = new InheritableThreadLocal();
local.set("123");
System.out.println(local.get());
new Thread(()->{
for (int i = 0; i < 2; i++) {
try {
System.out.println(local.get());
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
local.set("456");
System.out.println(local.get());
}
}
//打印
123--main线程
456--main线程
123--子线程
123---子线程
示例2:改了子线程的值,父线程还是旧值
public class Test6{
public static void main(String[] args) throws InterruptedException {
InheritableThreadLocal local = new InheritableThreadLocal();
local.set("123");
System.out.println("线程:"+Thread.currentThread().getName()+local.get());
new Thread(()->{
for (int i = 0; i < 2; i++) {
try {
if (i == 1){
local.set("456");
}
System.out.println("线程:"+Thread.currentThread().getName()+local.get());
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Thread.sleep(1000);
System.out.println("线程:"+Thread.currentThread().getName()+local.get());
}
}
//打印
线程:main123
线程:Thread-0123
线程:Thread-0456
线程:main123
示例3:子线程可以感应对象属性值的变化
class userInfos{
private String userName;
public userInfos(String userName) {
this.userName = userName;
}
}
public class Test6{
public static void main(String[] args) throws InterruptedException {
InheritableThreadLocal<userInfos> local = new InheritableThreadLocal<>();
userInfos userInfo = new userInfos("A");
local.set(userInfo);
new Thread(()->{
for (int i = 0; i < 4; i++) {
try {
System.out.println("线程:"+Thread.currentThread().getName()+local.get().getUserName());
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Thread.sleep(100);
userInfo.setUserName("B");
}
}
//打印
线程:Thread-0A
线程:Thread-0A
线程:Thread-0B
线程:Thread-0B
重写childValue()方法实现对继承的值进行加工
在继承的同时还可以对值进行进一步的加工:
public class Test6 extends InheritableThreadLocal{
@Override
protected Object childValue(Object parentValue) {
return parentValue+"----";
}
public static void main(String[] args) throws InterruptedException {
Test6 test6 = new Test6();
test6.set("123");
System.out.println(Thread.currentThread().getName()+test6.get());
new Thread(()-> System.out.println(Thread.currentThread().getName()+test6.get())).start();
}
}
//打印
main123
Thread-0123----