一.java创建多线程的几种常用方式
1.继承Thread类
创建一个线程类并继承Thread ,并覆盖run方法,然后在启动线程的时候调用start
public class MyThread extends Thread {public MyThread() {}public void run() {for(int i = 0; i < 10; i ++) {System.out.println(Thread.currentThread().getName() + ":" + i);}}public static void main(String[] args) {MyThread myThread1 = new MyThread();MyThread myThread2 = new MyThread();MyThread myThread3 = new MyThread();myThread1.start();myThread2.start();myThread3.start();}
}
2.实现Runnable接口
创建一个线程类并实现Runnable接口 ,并实现run方法,然后在启动线程的时候调用start
public class MyRunable implements Runnable{public void run() {for(int i = 0; i < 10; i ++) {System.out.println(Thread.currentThread().getName() + ":" + i);}}public static void main(String[] args) {MyRunable myRun = new MyRunable();MyThread myThread1 = new Thread(myRun);MyThread myThread2 = new Thread(myRun);MyThread myThread3 = new Thread(myRun);myThread1.start();myThread2.start();myThread3.start();}
}
3.实现Callable接口
创建一个线程类,实现Callable接口,并实现call方法,然后在启动线程的时候调用start
callable接口是有返回值的,借助FutureTask类能够实现判断任务是否中断,中断任务以及任务是否完成
public class MyCallnable implements Callable<String> {public String call() throws Exception {for (int i = 0; i < 10; i++) {System.out.println("myCallable线程正在执行:"+i);}return "MyCallabe线程执行完毕";}public static void main(String[] args) {//创建futuretask对象FutureTask<String> futureTask = new FutureTask<String>(new MyCallnable());//创建Thread对象,传入futureTaskThread thread1 = new Thread(futureTask);Thread thread2 = new Thread(futureTask);Thread thread3 = new Thread(futureTask);thread1.start();thread2.start();thread3.start();}
}
4.Executors利用线程池来创建线程
Executors提供五种线程池,分别为:
- newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
- newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
- newWorkStealingPool:创建一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。
下面我们就来重点说下线程池
二.单线程的弊端
- 每次创建线程,销毁线程性能差,占用系统资源。
- 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
- 缺乏更多功能,如定时执行、定期执行、线程中断、线程状态返回等等。
三.线程池
1.什么是线程池
通常我们使用线程的时候就去创建一个线程,执行完了然后再回收释放资源,但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程释放线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务,其他的任务就可以复用现成的线程资源,不必再创建销毁,浪费系统资源,那么就有了线程池这个东西。
2.线程池有哪些优点
- 降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
- 提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
- 方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。
- 根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果。
3.Executor中的几个核心类
- Executor接口:声明了execute(Runnable runnable)方法,执行任务代码
- ExecutorService接口:继承Executor接口,声明方法:submit、invokeAll、invokeAny以及shutDown等
- AbstractExecutorService抽象类:实现ExecutorService接口,基本实现ExecutorService中声明的所有方法
- ScheduledExecutorService接口:继承ExecutorService接口,声明定时执行任务方法
- ThreadPoolExecutor类:继承类AbstractExecutorService,实现execute、submit、shutdown、shutdownNow方法
- ScheduledThreadPoolExecutor类:继承ThreadPoolExecutor类,实现ScheduledExecutorService接口并实现其中的方法
- Executors类:提供快速创建线程池的方法
4.常用线程池
Executors提供五种线程池,分别为:
- newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
- newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
- newWorkStealingPool:创建一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。
5.线程池中的参数
-
corePoolSize(线程池基本大小):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。)
-
maximumPoolSize(线程池最大大小):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。
-
keepAliveTime(线程存活保持时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。
-
workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。
-
threadFactory(线程工厂):用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。
-
handler(线程饱和策略):当线程池和队列都满了,再加入线程会执行此策略。
6.任务执行流程
1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
3.当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
4.当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
5.当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,释放空闲线程
6.当设置allowCoreThreadTimeOut(true)时,该参数默认false,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭
7.execute和submit的区别
- execute(),执行一个任务,没有返回值。
- submit(),提交一个线程任务,有返回值。
8.线程池中的工作队列
-
ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
-
LinkedBlockingQueue:是一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列
-
SynchronousQueue:是一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
-
PriorityBlockingQueue:是一个具有优先级的无限阻塞队列。
9.线程任务的拒绝策略
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
- ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
- ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
- ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务