线程组
线程组概述
Java中用
ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。默认情况下,所有的线程都属于主线程组。
public final ThreadGroup getThreadGroup()通过线程对象获取所属的线程组public final String getName()通过线程组对象获取线程组的名字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr, "张三");
Thread t2 = new Thread(mr, "李四");
//获取线程组
// 线程类里面的方法:public final ThreadGroup getThreadGroup()
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
// 线程组里面的方法:public final String getName()
String name1 = tg1.getName();
String name2 = tg2.getName();
System.out.println(name1);
System.out.println(name2);
// 通过结果我们知道了:线程默认情况下属于main线程组
// 通过下面的测试,你应该能够看到,默任情况下,所有的线程都属于同一个组
System.out.println(Thread.currentThread().getThreadGroup().getName());给线程设置线程组
ThreadGroup(String name)线程组对象的构造器并赋值名字Thread(ThreadGroup group, Runnable target,String name)线程对象的构造器,直接设置线程组
1
2
3
4
5
6
7
8
9
10
11
12
13// ThreadGroup(String name)
ThreadGroup tg = new ThreadGroup("这是一个新的组");
MyRunnable mr = new MyRunnable();
// Thread(ThreadGroup group, Runnable target, String name)
Thread t1 = new Thread(tg, mr, "张三");
Thread t2 = new Thread(tg, mr, "李四");
System.out.println(t1.getThreadGroup().getName());
System.out.println(t2.getThreadGroup().getName());
//通过组名称设置后台线程,表示该组的线程都是后台线程
tg.setDaemon(true);
线程池
为什么会有线程池?(线程池概述)
- 程序
创建一个新的线程成本较高,因为它涉及到要与操作系统进行交互。频繁的线程创建和销毁,大大消耗时间和降低系统的效率。 - 线程池的使用解决了这个问题,它使得
多个线程能够一次创建完,放在线程池中,执行完后并不会被销毁,而是再次回到线程池中变成空闲状态,等待下一个对象来使用。并且即拿即用,不用每次都创建,大大提高了线程的复用性,提高系统效率。 - JDK1.5开始,Java有了内置的线程池。
Executors工厂类
内置线程池
JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法:
public static ExecutorService newFixedThreadPool(int nThreads)创建一个
可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。参数: nThreads - 池中的线程数
返回: 新创建的线程池
public static ExecutorService newSingleThreadExecutor()创建一个使用
单个 worker 线程的 Executor,以无界队列方式来运行该线程Executors.newCachedThreadPool()创建一个
缓冲池,缓冲池容量大小为Integer.MAX_VALUE
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。
下面是这三个静态方法的具体实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
根据源码的具体实现来看,它们实际上也是调用了ThreadPoolExecutor,只不过参数都已配置好了。
ThreadPoolExecutor构造器如下:
1 | public ThreadPoolExecutor(int corePoolSize, |
参数含义:
- corePoolSize - 池中所保存的线程数,包括空闲线程
- maximumPoolSize - 池中允许的最大线程数。
- keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
- unit - keepAliveTime 参数的时间单位。
- workQueue - 执行前用于保持任务的队列。此队列仅由保持 execute 方法提交的 Runnable 任务。
- handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
方法源码分析:
newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,也使用的LinkedBlockingQueue;newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。- 实际中,如果Executors提供的三个静态方法能满足要求,就尽量使用它提供的三个方法,因为自己去手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数量来进行配置。
- 另外,如果ThreadPoolExecutor达不到要求,可以自己继承ThreadPoolExecutor类进行重写。
ExecutorService提供了如下方法:Future<?> submit(Runnable task)提交一个
Runnable 任务用于执行,并返回一个表示该任务的Future。该 Future 的 get 方法在成功 完成时将会返回 null。 参数: task - 要提交的任务返回: 表示任务等待完成的 Future
<T> Future<T> submit(Callable<T> task)提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。该 Future 的 get 方法在成功完成时将会返回该任务的结果。
void shutdown()启动一次顺序关闭,执行以前提交的任务,但
不接受新任务。如果已经关闭,则调用没有其他作用。
- 代码示例:
1 | ExecutorService pool = Executors.newFixedThreadPool(2); |
使用线程池的风险
虽然线程池是构建多线程应用程序的强大机制,但使用它并不是没有风险的。使用线程池构建的应用程序容易遭受任何其他多线程应用程序容易遭受的所有并发风险,例如同步错误、死锁、资源不足和线程泄露。
Future 接口
Future 表示异步计算的结果。
- 它提供了
检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。 - 计算完成后只能使用
get方法来获取结果,如有必要,计算完成前可以阻塞此方法。 - 取消则由
cancel方法来执行。 - 还提供了其他方法,以确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。
- 如果为了可取消性而使用
Future但又不提供可用的结果,则可以声明Future<?>形式类型、并返回null作为底层任务的结果。 FutureTask是其实现类
| 方法摘要 | |
|---|---|
boolean |
cancel(boolean mayInterruptIfRunning) 试图取消对此任务的执行。 |
V |
get() 如有必要,等待计算完成,然后获取其结果。 |
V |
get(long timeout, TimeUnit unit) 如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。 |
boolean |
isCancelled() 如果在任务正常完成前将其取消,则返回 true。 |
boolean |
isDone() 如果任务已完成,则返回 true。 |
实现多线程的第三种方式
- 步骤:
- 创建实体类,实现
Callable接口 - 实现接口中的
call()方法 - 利用
ExecutorService线程池对象 的<T> Future<T> submit(Callable<T> task()方法提交该Callable接口的线程任务。
- 创建实体类,实现
1 | // 创建线程池 |
- 利用匿名内部类方式:
1 | ExecutorService service = Executors.newSingleThreadExecutor(); |
- Lambda表达式方式:
1 | public class CallableTest { |
- 实现callable接口,提交给
ExecutorService返回值是异步执行的。 - 该方式的优缺点:
- 优点:
有返回值可以抛出异常
- 缺点:
- 代码较复杂,需要利用线程池
- 优点: