java常见线程创建启动
在Java开发中常用的线程启动有四种
- 继承Thread类,并重写run()方法 通过start()调用
- 实现Runnable接口并实现run()方法 通过new Thread(myThread2).start()调用
- 继承Callable接口并实现call()方法,通过 new Thread(new FutureTask(myThread3)).start()方式启动
- 通过线程池创建线程并启动,通常使用 Executors下的静态方法(如Executors.newFixedThreadPool(3))创建线程池,并使用 Executor::execute()方法加入线程
public class ThreadTest {
private static int POOL_NUM = 10; //线程数量
//private static AtomicInteger num = new AtomicInteger(0);
private static Integer num = new Integer(0);
private static Object lock = new Object();
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("执行main线程:"+Thread.currentThread().getName());
MyThread myThread = new MyThread();
myThread.start();
Runnable myThread2 = new MyThread2();
Thread thread = new Thread(myThread2);
thread.start();
Callable myThread3 = new MyThread3();
for (int i = 0; i < 4; i++) {
FutureTask<Integer> task = new FutureTask<Integer>(myThread3);
Thread thread1 = new Thread(task);
thread1.start();
System.out.printf("执行Callable子线程的返回值:%d\n",task.get());
}
//通过线程池创建线程
ExecutorService executorService = Executors.newFixedThreadPool(3);//设置现线程池大小
for (int i = 0; i < POOL_NUM; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("通过线程池创建的线程:"+Thread.currentThread().getName());
}
});
}
}
//方式1 继承Thread类,并重写run()方法 通过start()调用
static class MyThread extends Thread{
@Override
public void run() {
System.out.println("执行Thread子线程:"+Thread.currentThread().getName());
}
}
//方式2 实现Runnable接口并实现run()方法 通过new Thread(myThread2).start()调用
static class MyThread2 implements Runnable{
@Override
public void run() {
System.out.println("执行Runnable子线程:"+Thread.currentThread().getName());
//throw new RuntimeException("Runnable子线程抛出异常"); //异常无法抛出到线程外
}
}
//方式3 继承Callable接口并实现call()方法,通过 new Thread(new FutureTask(myThread3)).start()方式启动
static class MyThread3 implements Callable<Integer>{
int i;
@Override
public Integer call() throws Exception {
System.out.println("执行Callable子线程:"+Thread.currentThread().getName());
return ++i;
}
}
}
继承Thread类
- 优点:使用简单,继承Thread类后可直接new对象并调用start()方法启动线程。
- 缺点:因为java单继承的关系,继承Thread类后就没有办法继承其他类,这样使得类的扩展性降低。且没有返回值,无法抛出异常。
实现Runnable接口
- 优点:因为是接口原因,所以不会限制实现类的可扩展性,且Thread本身也是继承了Runnable接口。
- 缺点:没有返回值,无法抛出异常。实现后依然需要使用Thread启动线程。
继承Callable接口
- 优点:有返回值,且可以抛出异常。
- 缺点:调用略微有一些麻烦。
通过线程池创建线程并启动
通常使用Executors类创建线程池,其主要职责就是创建线程池。
其中包含四种主要线程池:
-
newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
-
newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
-
newScheduledThreadPool:创建一个可定期或者延时执行任务的定长线程池,支持定时及周期性任务执行。
-
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
这里可以发现四种常用的线程池其最终实现都是由ThreadPoolExecutor实现。这就不得不提到ThreadPoolExecutor了
ThreadPoolExecutor
ThreadPoolExecutor是Executor线程池创建接口的实现类,其特点是构造函数提供了创建线程池所需要的7个参数:
- corePoolSize:核心线程数
- 核心线程会一直存活,及时没有任务需要执行
- 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
- 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
- maxPoolSize:最大线程数
- 当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
- 当线程数=maxPoolSize,且任务队列已满时,线程池会采用拒绝策略处理
- keepAliveTime:线程空闲时间
- 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
- 如果allowCoreThreadTimeout=true,则会直到线程数量=0
- TimeUnit unit:表示keepAliveTime的单位:
- workQueue:任务队列容量(阻塞队列)
- 当核心线程数达到最大时,新任务会放在队列中排队等待执行
- threadFactory:指定创建线程的工厂
- handler:任务拒绝处理器
- 两种情况会拒绝处理任务:
- 当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务
- 当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务,线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常
- 两种情况会拒绝处理任务:
注意:allowCoreThreadTimeout:允许核心线程超时,是ThreadPoolExecutor的属性之一
这七个参数中重点注意两个点:workQueue和handler
- workQueue:任务队列有很多种实现方式,各有不同,随使用者抉择。其中重点说3个ThreadPoolExecutor线程池的API文档中推荐使用的
- SynchronousQueue :(不存储元素的阻塞队列)每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于 阻塞状态,
吞吐量通常要高于LinkedBlockingQueue
静态工厂方法 Executors.newCachedThreadPool 使用了这个队列。 - LinkedBlockingQueue:(基于单向链表的无界的阻塞队列,尾部插入元素,头部取出元素)可以指定容量,也可以不指定容量。由于它具有“无限容量”的特性,所以可以看作位无界队列。
- ArrayBlockingQueue:(基于数据结构的有界的阻塞队列)基于数组结构的有界阻塞队列。按 FIFO(先进先出)原则对元素进行操作。尾部插入元素,头部取出元素。这是一个典型的“有界缓存区”,数组的大小一旦固定,就不能再增加其容量。试图向 已满队列 中放入元素会导致操作受阻塞;试图从 空队列 中提取元素将导致类似阻塞。
- SynchronousQueue :(不存储元素的阻塞队列)每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于 阻塞状态,
剩余的队列实现可在源码中查看
2. handler:拒绝策略,在线程池已满且任务队列已满的情况加,采用阻塞策略,阻塞策略一共有四种
- AbortPolicy:直接抛出异常(默认)
- CallerRunsPolicy:直接使用当前线程执行次任务
- DiscardOldestPolicy:讲任务队列中第一个丢弃,新任务加入到任务队列
- DiscardPolicy:什么都不做