1. 多线程的优缺点
优点:
- 提升cpu使用率
- 异步处理任务,提升响应速度
缺点:
- 开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
- 多线程之间存在数据共享问题,提高技术复杂度
2. java开启多线程
-
继承Thread类,重写 run() 方法
-
实现Runnable接口,重写 run() 方法
-
实现Callable接口,重写 call() 方法
3. 线程池
“线程池”顾名思义,就是存放线程的池子,这个池子可以存放多少线程取决于采用哪种线程池,取决于有多少并发线程,有多少计算机的硬件资源。使用线程池最直接的好处就是:线程可以重复利用、减少创建和销毁线程所带来的系统资源的开销,提升性能(节省线程创建的时间开销,使程序响应更快)
4. JDK的4种的线程池(JDK1.5之后)
1.单个线程的线程池。
ExecutorService executorService = Executors.newSingleThreadExecutor();
核心线程数和最大线程数均设置为1,使用的队列是无界队列LinkedBlockingQueue
2.固定线程数的线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
这种线程池里面的线程被设计成存放固定数量的线程,具体线程数可以考虑为CPU核数*N(N可大可小,取决于并发的线程数,计算机可用的硬件资源等)。可以通过下面的代码来获取当前计算机的CPU的核数。
int processors = Runtime.getRuntime().availableProcessors();
3.任务相关的线程池
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
除了线程池的特性以外,可以实现循环或延迟任务,可用于实现任务调度
4.回收型线程池
ExecutorService executorService = Executors.newCachedThreadPool();
核心池大小为0,线程池最大线程数目为最大整型。使用的是阻塞队列SynchronousQueue。该线程池适合处理比较小的任务,如果任务处理时间较长,那么在不断提交任务的情况下,会导致严重的性能问题。
5. 自定义一个线程池
jdk的线程池虽好,但是阿里Java开发手册中强烈要求我们不允许使用Executors来创建线程池对象,而要通过ThreadPoolExecutor的方式。
先了解线程池相关的参数以及概念
1.ThreadPoolExecutor的参数
参数 | 说明 |
corePoolSize | 核心线程数 |
maximumPoolSize | 最大线程数量 |
keepAliveTime | 存活时间 |
timeUnit | 时间单位 |
workQueue | 缓存线程的队列 |
threadFactory | 创建线程的工厂 |
rejectedExecutionHandler | 线程池满了的拒绝策略 |
线程池 - 核心线程数
cpu密集型任务,就需要尽量压榨cpu,参考值可以设为NCPU+1
IO密集型任务,参考值可以设置为2*NCPU
2.线程池的队列
JDK提供了7个阻塞队列。(也属于并发容器)
- ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
- PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
- DelayQueue:一个使用优先级队列实现的无界阻塞队列。
- SynchronousQueue:一个不存储元素的阻塞队列。
- LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
- LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
阻塞队列是一个在队列基础上又支持了两个附加操作的队列。
2个附加操作:
支持阻塞的插入方法:队列满时,队列会阻塞插入元素的线程,直到队列不满。
支持阻塞的移除方法:队列空时,获取元素的线程会等待队列变为非空。
3.线程池的拒绝策略
ThreadPoolExecutor默认有四个拒绝策略:
1、ThreadPoolExecutor.AbortPolicy()
直接抛出异常RejectedExecutionException
2、ThreadPoolExecutor.CallerRunsPolicy()
直接调用run方法并且阻塞执行
3、ThreadPoolExecutor.DiscardPolicy()
直接丢弃后来的任务
4、ThreadPoolExecutor.DiscardOldestPolicy()
丢弃在队列中队首的任务
当然可以自己继承RejectedExecutionHandler来写拒绝策略.
自定义线程池代码示例:
package com.example.demo.yuwen;
import java.util.concurrent.*;
public class MyThreadPool {
/**
* 核心线程数
*/
private static final int CORE_SIZE = 5;
/**
* 最大线程数
*/
private static final int MAX_SIZE = 10;
/**
* 核心线程数额外的线程存活时间(核心线程数不会杀死)
*/
private static final int KEEP_LIVE_TIME = 60;
/**
* 线程池队列长度
*/
private static final int QUEUE_SIZE = 256;
private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = createPool();
/**
* 创建一个线程池
*
* @return ThreadPoolExecutor
*/
public static ThreadPoolExecutor createPool() {
return new ThreadPoolExecutor(CORE_SIZE, MAX_SIZE, KEEP_LIVE_TIME, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(QUEUE_SIZE), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
}
/**
* 提交任务
*
* @param task task
* @return Future
*/
public static Future submit(Callable task) {
return THREAD_POOL_EXECUTOR.submit(task);
}
}
package com.example.demo.yuwen;
import java.util.concurrent.Callable;
public class MyThread implements Callable {
/**
* 自己可以定义多个参数,使用构造方法赋值
*/
private String yourParam;
public MyThread(String yourParam) {
this.yourParam = yourParam;
}
@Override
public Object call() throws Exception {
System.out.println("thread is work");
return yourParam;
}
}
测试代码:
package com.example.demo.yuwen;
import java.util.concurrent.Future;
public class Demo {
public static void main(String[] args) throws Exception {
Future ret = MyThreadPool.submit(new MyThread("china"));
System.out.println(ret.get());
}
}
结果如下: