线程组和线程池
线程组
1.线程组介绍
java中用线程组(ThreadGroup)来同时管理一批线程,对线程组做的操作相当于对里面所有的线程都做了同样的操作,线程所属的线程组在运行过程中无法修改。可能大家听到更多的是线程池的概念,但其实只要使用过线程,那么你就已经使用过了ThreadGroup,只是你自己并不知道而已,我们看下Thread类的构造方法:
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
可以看到,这个构造方法只需要指定target对象,我们再具体看下init()里面的操作:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name.toCharArray();
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
我们主要看方法中对ThreadGroup g参数的处理:
hread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
可以看到,虽然我们在创建线程实例的时候并没有指定线程所属的线程组,但是在调用Thread类的构造方法创建实例的时候会进行判断,如果没有指定线程组,会使用默认的线程组,如果是在main方法中的创建的线程,那么它们的默认线程组就是main,简单写个测试类看下:
package com.ljw.Thread.ThreadGroup;
/**
* Created by liujiawei on 2018/7/10.
*/
public class TestThreadGroup {
public static void main(String[] args) {
B b = new B();
Thread t = new Thread(b);
System.out.println("main线程所属线程组:" + Thread.currentThread().getThreadGroup().getName());
System.out.println("main线程中的t线程所属线程组:" + t.getThreadGroup().getName());
}
}
运行结果:
2.线程组使用
首先介绍一下几个常用的方法:ThreadGroup类提供了两个公共的构造方法:
public ThreadGroup(String name) {
this(Thread.currentThread().getThreadGroup(), name);
}
public ThreadGroup(ThreadGroup parent, String name) {
this(checkParentAccess(parent), parent, name);
}
ThreadGroup是一个类似树的结构,如果创建ThreadGroup实例的时候没有指定parent,那么就使用当前线程所属的线程组作为parent。
同时Thread类也提供了几个构造方法用来指定线程的所属线程组:
public Thread(ThreadGroup group, Runnable target, String name) {
init(group, target, name, 0);
}
public Thread(ThreadGroup group, String name) {
init(group, null, name, 0);
}
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
主要就是三个参数ThreadGroup group,Runnable target和String name,不同参数构成不同的构造方法,最终调用的都是同一个方法来创建实例。
ThreadGroup类同时还提供几个常用方法来操作线程组中的线程:
- 获取parent:(system是最顶级的线程组)
public final ThreadGroup getParent()
- 设置最大优先级:
public final void setMaxPriority(int pri)
- 获取优先级:
public final int getMaxPriority()
- 中断所有线程:
public final void interrupt()
- 返回活动线程的数量:
public int activeCount()
- 设置为后台线程:
public final void setDaemon(boolean daemon)
- 是否是后台线程:
public final boolean isDaemon()
思考一个问题,我们知道线程是可以设置优先级的,如果所属的线程组也设置了优先级,以哪个为准呢?
我们看下ThreadGroup中setMaxPriority()方法的源码:
public final void setMaxPriority(int pri) {
int ngroupsSnapshot;
ThreadGroup[] groupsSnapshot;
synchronized (this) {
checkAccess();
if (pri < Thread.MIN_PRIORITY || pri > Thread.MAX_PRIORITY) {
return;
}
maxPriority = (parent != null) ? Math.min(pri, parent.maxPriority) : pri;
ngroupsSnapshot = ngroups;
if (groups != null) {
groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);
} else {
groupsSnapshot = null;
}
}
for (int i = 0 ; i < ngroupsSnapshot ; i++) {
groupsSnapshot[i].setMaxPriority(pri);
}
}
线程组最大优先级的设定:
•系统线程组的最大优先级默认为Thread.MAX_PRIORITY
•创建线程组的时候其最大优先级默认为父线程组(如果未指定父线程组,则其父线程组默认为当前线程所属线程组)的最大优先级
•可以通过setMaxPriority更改最大优先级,但无法超过父线程组的最大优先级
3.异常处理
线程的异常处理也是比较重要的一块,如果异常得不到,线程获取的资源就会一直不释放。所以Thread类提供了一个内部接口用来处理线程遇到的异常:public interface UncaughtExceptionHandler {
/**
* Method invoked when the given thread terminates due to the
* given uncaught exception.
* <p>Any exception thrown by this method will be ignored by the
* Java Virtual Machine.
* @param t the thread
* @param e the exception
*/
void uncaughtException(Thread t, Throwable e);
}
我们在创建完Thread类的实例以后,可以通过调用setUncaughtExceptionHandler()方法,通过内部实现类的形式定义对异常的处理,这是Thread类对异常的处理,我们上文也介绍了线程组ThreadGroup是用来管理一组线程并同时对他们进行操作的。那么ThreadGroup中有没有方法可以对线程组中的线程抛出未处理的异常做处理呢?
答案是肯定的,ThreadGroup类提供了uncaughtException(thread,throwable)方法
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
我们可以在创建完ThreadGroup的实例重写这个方法来处理异常:
ThreadGroup group = new ThreadGroup("线程组"){
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("线程组的异常处理机制处理了" + t.getName() + "的异常");
}
};
说了这么多我们看一下代码实例:
两个实现了Runnable接口类:
A:
package com.ljw.Thread.ThreadGroup;
/**
* Created by liujiawei on 2018/7/10.
*/
public class A implements Runnable{
@Override
public void run() {
int a = 5 / 0;
}
}
B:
package com.ljw.Thread.ThreadGroup;
/**
* Created by liujiawei on 2018/7/10.
*/
public class B implements Runnable{
@Override
public void run() {
int a = 5/0;
}
}
测试类:
package com.ljw.Thread.ThreadGroup;
/**
* Created by liujiawei on 2018/7/10.
*/
public class TestThreadGroup {
public static void main(String[] args) {
ThreadGroup group = new ThreadGroup("线程组"){
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("线程组的异常处理机制处理了" + t.getName() + "的异常");
}
};
A a = new A();
B b = new B();
Thread threadA = new Thread(group,a,"A线程");
Thread threadB = new Thread(group,b,"B线程");
threadA.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("这是" + t.getName() + "的异常处理");
}
});
threadA.start();
threadB.start();
}
}
我们对线程实现了内部处理异常,对b没有做处理,看下运行结果:
从结果中我们可以看到,a线程的未处理的异常被自己处理,b线程中的异常则是被线程组给处理了。
我们总结一下线程组对于线程中未处理而抛出的异常的处理机制:
(1) 如果线程组的父类有异常处理机制,那么优先选择父类的异常处理机制;
(2) 线程组中的线程如果有自己的异常处理,优先选择自身的异常处理;
(3) 如果异常对象是ThreadDeath对象,则不做处理,否则将异常跟踪栈的信息打印到System.err错误输出流,并结束线程。
线程池(简单入门)
1.线程池介绍
创建一个线程集合,当需要使用线程的时候去这个集合中取出线程执行,用完再放回去。2.线程池的好处
(1) 控制系统中线程并发的数量;(2)节省创建线程的成本;
3.线程池的基本使用方法
线程池最核心的类就是ThreadPoolExecutor类,他提供了四个构造方法用来创建线程池实例:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
可以看到前面三个构造方法最后调用的都是最后一个,我们看下这个方法的几个参数分别代表什么:
- corePoolSize:核心池大小;
- maxmunPoolSize:线程池最大执行数;
- keepAliveTime:线程没有任务执行时保持多久终止;
- unit:keepAliveTime的单位,使用枚举TimeUnit中定义的时间常量;
- workQueue:阻塞队列,用来存储等待执行的任务
- threadFactory:线程工厂,用来创建线程;
- handler:拒绝任务时的策略,有:
- ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
- ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
- ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
一般不直接通过ThreadPoolExecutor的构造方法创建线程池,而是通过调用Executors中的提供的几个静态方法来创建线程池实例,返回一个ExecutorService对象,主要有以下几种线程池:
newFixedThreadPool(int nThreads)
创建指定线程数量的线程池
newCachedThreadPool()
创建一个弹性缓存线程池,初始时线程数量为0,只有通过execute或者submit提交任务时,才会创建线程
newScheduledThreadPool
创建一个定时器线程池,在执行延迟后执行任务
newSingleThreadExecutor()
创建一个单线程线程池,每次只有一个线程任务,其他任务会等待完成,用来保证执行的顺序
我们简单写个测试类进行实践:
线程类A:
package com.ljw.Thread.ThreadPool;
/**
* Created by liujiawei on 2018/7/9.
*/
public class A implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "==" + i);
}
}
}
测试类:
package com.ljw.Thread.ThreadPool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by liujiawei on 2018/7/9.
*/
public class TestThreadPool {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
Thread threadA = new Thread(new A(),"A");
Thread threadB = new Thread(new A(),"B");
threadPool.submit(threadA);
threadPool.submit(threadB);
threadPool.shutdown();
}
}
运行结果:
可以看到两个线程的执行体都成功执行了。
线程池工作流程:
我们总结下使用线程池执行线程任务的步骤:
(1) 通过Executors的静态方法创建一个ExecutorService对象,这个对象代表线程池;
(2)创建线程实例;
(3)通过submit()方法提交线程任务;
(4)通过shutdown()方法关闭线程池。