目录
线程池创建的目的在于:如果我们直接采用像之前的Thread构造方法创建线程,其实在每次创建和销毁线程时都有一定的开支,当线程太多时这个开支还是挺明显的。
“池”:目的就是让某些对象被多次重复利用,减少频繁创建和销毁对象带来的开支问题
所以说,线程池最大的好处就是可以减少每次启动和销毁线程的损耗(提高时间和空间利用率)
线程池内部创建了若干个线程,这些线程都是Runnable的状态,只需要从系统中取出任务(run),就可以立即开始执行。
我们将线程池比作一个餐厅:
餐厅中的固定员工:线程池中的线程
后面招聘的临时员工:当线程池中的线程不够用时,可产生临时员工。
执行任务方法:excute

提交任务方法:submit
提交一个任务到线程池(线程的run或者call方法),池中会派遣空闲线程执行任务


终止线程方法:shutdown
立即终止所有线程:shutdownNow(); 无论线程是否在空闲状态

停止在空闲态的线程,运行线程在结束后停止:shutdown();
这个类可以创建JDK内置的四大线程池
加s的基本都是工具类 比如Arrays(数组工具类,copyOf,sort等等)
- //固定大小的线程池
- ExecutorService pool = Executors.newFixedThreadPool(10);
采用.submit()方法执行任务
- pool.submit(new Runnable() {
- @Override
- public void run() {
- for (int i = 0; i < 3; i++) {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName()+"绕"+i+"圈");
- }
- }
- });
工具类源码:

- //数量动态变化的缓存池
- ExecutorService pool1 = Executors.newCachedThreadPool();
工具类源码:
橘色的参数基本是用不到的,因为最大线程数有四十多亿

ps:单线程池的意义在于,省去创建线程和销毁线程的时间,来一个任务执行一个任务,还未执行的任务在工作队列排队执行。
- //只包含一个线程的单线程池
- ExecutorService pool2 = Executors.newSingleThreadExecutor();
工具类源码:

ps:使用的是定时器线程接口,线程池核心接口的子接口
- //定时器线程池
- ScheduledExecutorService pool3 = Executors.newScheduledThreadPool(10);
采用.schedule接收任务,可规定延迟多久执行任务
- pool3.schedule(new Runnable() {
- @Override
- public void run() {
- for (int i = 0; i < 3; i++) {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName()+"绕"+i+"圈");
- }
- }
- },3, TimeUnit.SECONDS);
工具类源码:

ExecutorService:线程池的核心接口,提供excute方法,submit方法,shutdown方法
ScheduledExecutorService:定时器接口,线程池核心接口的子接口,追加.schedule方法,可以延迟启动任务
ThreadPoolExector类:实现核心接口的子类,也是线程池接口的最常用实现子类
Executors:线程池的工具类,提供了ThreadPoolExector的对象,内置创建好的四大线程池。

一图流

此工作流程基于submit提交一个新任务时线程池内部的执行流程
首先判断核心池是否已经拉满,没拉满我们可以再创建一个固定线程(正式工)执行此任务
若已经拉满,我们就去判断工作队列是否已满,没有满的话,我们就在队列中排队等待,
否则,进行线程池的最大数量判断,如果没满的话就创建一个空闲线程来执行此任务,
否则,若线程池已经达到了最大数量,说明此时线程池已经达到最大负荷了,我们就需要执行拒绝策略了
开始执行

若已经达到核心池最大数量

若队列已经满了

当线程数量达到上线,执行拒绝策略

synchronized最开始就是乐观锁,当竞争激烈就变成悲观锁。
每次读写数据都认为不会发生冲突,不会阻塞,一般来说只有在数据更新时才会检查是否发生冲突,若没有冲突直接更新,有冲突再去解决冲突。
当线程冲突不严重时,可以采用乐观锁策略来避免多次的加锁解锁操作
eg:
乐观锁一般通过版本号机制来实现

比如这两个线程,如果线程1先结束并将主内存的版本从version1变成version2时,线程2写回主内存检测到主内存的版本和自己的工作内存版本对不上,就会直接报错,不写回。

当下次再进行线程操作时,就会读取新的版本号,这个时候重新进行操作,尝试写回。

每次进行读写数据都会冲突,都需要尝试加锁操作,保证同一时间只有一个线程在读写数据。
当线程冲突严重时,就需要加锁,来避免线程频繁访问共享数据失败带来的CPU空转问题
特别适用于线程基本都在读数据,很少有些数据的情况。
多数据在访问数据时,并发读数据不会冲突,只有在写数据时才有可能发生冲突,所有就有了读写锁的概念,JDK内置的读写锁是ReentrantReadWriteLock,用这个可实现读写操作。
1.多个线程并发读数据时,都可以访问到读锁,并发执行不互斥。
2.多个线程写数据时,两个线程互斥,只有一个能访问到写锁,其他线程阻塞。
3.一个线程读,另一个线程写,也会互斥,当写的过程结束读线程才能继续执行。
需要操作系统和硬件支持,线程获取重量级锁失败进入阻塞状态(os,用户态切换到内核态,开销非常大)
eg:
比如去银行办理业务
用户态:在窗口外自己处理的业务
内核态:窗口内部,需要工作人员协助
这样来回切换是非常耗时的
尽量在用户态执行操作,线程不阻塞,不会进行状态切换。
轻量级锁的常用实现是采用自旋锁:
之前的方式是,获取锁失败就进入Blocked状态,线程阻塞等待锁释放,然后由CPU唤醒(这个时间一般都比较长,由用户态切换到内核态)。
自旋锁:
就是一个循环的概念
while(获取lock==false)就进行死循环,但不会让出CPU,线程也不会阻塞,不会切换状态,线程就在CPU上空跑,直到锁被释放,就可以很快速地获取到锁。
获取锁失败的线程进入等待队列,当锁被释放,在队列中等待时间最长的线程首先获取到锁。
阻塞队列的各个线程获取到锁的几率相等,不分先后。
偏向锁 ->轻量级锁 ->重量级锁