并发四-线程池
20. 共享模型之不可变
20.1 SimpleDateFormat与DateTimeFormatter
-
package com.sunyang.concurrentstudy; import lombok.extern.slf4j.Slf4j; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAccessor; /** * @program: ConcurrentStudy * @description: Dmeo * @author: SunYang * @create: 2021-08-08 16:00 **/ @Slf4j(topic = "c.Demo") public class SimpleDateFormatDemo { public static void main(String[] args) { // 安全 DateTimeFormatter stf = DateTimeFormatter.ofPattern("yyy-MM-dd"); for (int i = 0; i < 100; i++) { new Thread(() -> { TemporalAccessor parse = stf.parse("1997-05-09"); log.debug("{}", parse); }).start(); } // test(); } // 不安全 private static void test() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); for (int i = 0; i < 100; i++) { new Thread(() -> { try { log.debug("{}", sdf.parse("1997-05-09")); } catch (ParseException e) { log.error("{}", e); } }).start(); } } }
20.2 不可变设计
-
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0
20.2.1 final的使用
String类中所有的属性都是final的
- 属性用final修饰保证了该属性是只读的,不能修改,
- 类用final修饰保证了该类中的方法不能被改变,防止子类无意破坏不可变性。
- 用final修饰只能保证他的引用不被改变,不能保证它的内容不能被改变。
20.2.2 保护性拷贝
-
使用字符串时,也有一些跟修改相关的方法啊,比如 substring 等,那么下面就看一看这些方法是 如何实现的,就以 substring 为例:
-
public String substring(int beginIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } int subLen = value.length - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);}
-
发现其内部是调用 String 的构造方法创建了一个新字符串,再进入这个构造看看,是否对 final char[] value 做出 了修改:
-
public String(char value[], int offset, int count) { if (offset < 0) { throw new StringIndexOutOfBoundsException(offset); } if (count <= 0) { if (count < 0) { throw new StringIndexOutOfBoundsException(count); } if (offset <= value.length) { this.value = "".value; return; } } if (offset > value.length - count) { throw new StringIndexOutOfBoundsException(offset + count); } this.value = Arrays.copyOfRange(value, offset, offset+count);}
-
结果发现也没有,构造新字符串对象时,会生成新的 char[] value,对内容进行复制 。这种通过创建副本对象来避 免共享的手段称之为**【保护性拷贝(defensive copy)】**
20.3 享元模式
定义
- 英文名称:Flyweight pattern 当需要重用数量有限的同一对象时
- 最小化内存的使用,对相同值的对象进行共享。
体现
-
在JDK中Boolean ,Byte, Short, Integer, Long, Character 等包装类提供了valueOf方法,例如Long的valueOf会缓存在-128~127之间的Long对象,在这个范围之间会重用对象,大于这个范围,才会新建Long对象。
-
private static class LongCache { private LongCache(){} static final Long cache[] = new Long[-(-128) + 127 + 1]; static { for(int i = 0; i < cache.length; i++) cache[i] = new Long(i - 128); }}
注意:
- Byte,Short,Long 缓存的范围是**-128~127**
- Character缓存的范围是0-127
- Integer的默认范围是**-128到127**,最小值不能改变,但最大值可以通过调整虚拟机参数来改变
- Boolean只缓存了TRUE和FALSE
应用
- 除了包装类还有String类的串池,BigDecimal BigInteger,都是不可变对象,都利用了享元模型,单个方法都是线程安全的,组合不是。
DIY数据库连接池
-
QPS上千时,每次都需要重新创建和关闭数据库连接,性能消耗大,所以自己先创建好一批连接,放入连接池,使用完毕后再还回连接池,这样既节约了连接的创建和关闭时间。也实现了重用。
-
package com.sunyang.concurrentstudy;import lombok.extern.slf4j.Slf4j;import java.sql.*;import java.util.Map;import java.util.Properties;import java.util.concurrent.Executor;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicIntegerArray;/** * @program: ConcurrentStudy * @description: Demo * @author: SunYang * @create: 2021-08-08 16:58 **/@Slf4j(topic = "c.Demo")public class PoolDemo { public static void main(String[] args) { Pool pool = new Pool(2); for (int i = 0; i < 5; i++) { new Thread(() -> { Connection connection = null; try { connection = pool.borrow(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } finally { if (connection != null) { pool.free(connection); } } }).start(); } }}@Slf4j(topic = "c.Demo")class Pool { // 1. 连接池大小 private final int poolSize; // 2. 连接对象数组 private Connection[] connections; // 3. 连接状态数组 0 可用 1 不可用 private AtomicIntegerArray states; // 4. 构造方法 public Pool(int poolSize){ this.poolSize = poolSize; this.connections = new Connection[poolSize]; this.states = new AtomicIntegerArray(new int[poolSize]); for (int i = 0; i < poolSize; i++) { connections[i] = new MockConnection("连接" + (i+1)) ; } } // 5. 借连接 public Connection borrow() { while(true) { for (int i = 0; i < poolSize; i++) { if (states.get(i) == 0) { if (states.compareAndSet(i, 0, 1)) { log.debug("获取链接{}", connections[i]); return connections[i]; } } } synchronized (this){ try { log.debug("等待获取链接"); this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } // 6. 还连接 public void free(Connection connection) { for (int i = 0; i < poolSize; i++) { if (connections[i] == connection) { states.set(i, 0); synchronized (this){ log.debug("归还链接{}", connection); this.notifyAll(); } break; } } }}class MockConnection implements Connection{ private String name; public MockConnection(String name) { this.name = name; } @Override public String toString() { return "MockConnection{" + "name='" + name + '\'' + '}'; } // 省略不重要代码}
-
17:44:07 [Thread-2] c.Demo - 等待获取链接17:44:07 [Thread-1] c.Demo - 获取链接MockConnection{name='连接1'}17:44:07 [Thread-4] c.Demo - 等待获取链接17:44:07 [Thread-0] c.Demo - 获取链接MockConnection{name='连接2'}17:44:07 [Thread-3] c.Demo - 等待获取链接17:44:08 [Thread-0] c.Demo - 归还链接MockConnection{name='连接2'}17:44:08 [Thread-3] c.Demo - 获取链接MockConnection{name='连接1'}17:44:08 [Thread-4] c.Demo - 获取链接MockConnection{name='连接2'}17:44:08 [Thread-2] c.Demo - 等待获取链接17:44:08 [Thread-1] c.Demo - 归还链接MockConnection{name='连接1'}17:44:08 [Thread-2] c.Demo - 等待获取链接17:44:09 [Thread-4] c.Demo - 归还链接MockConnection{name='连接2'}17:44:09 [Thread-2] c.Demo - 获取链接MockConnection{name='连接2'}17:44:09 [Thread-3] c.Demo - 归还链接MockConnection{name='连接1'}17:44:10 [Thread-2] c.Demo - 归还链接MockConnection{name='连接2'}
-
没有考虑动态增长与收缩
-
连接保活(可用行检测)
-
等待超时处理
-
分布式hash
20.4 final原理
20.4.1 设置final变量原理
- 在final修饰的变量后的指令后会加入写屏障
20.4.2 读取final变量
- 读取静态变量是到堆内存中的CLass类对象(镜像类)中找的getstatic,1.8之前在C++语言类中,1.8之后再堆中类对象中,静态常量读取是将类对象中静态常量复制了一份到自己的栈中,就不用到堆中去找了BIPUSH,而成员变量是到堆中的实例对象中去寻找。效率提升。
20.5 无状态
- 在 web 阶段学习时,设计 Servlet 时为了保证其线程安全,都会有这样的建议,不要为 Servlet 设置成员变量,这 种没有任何成员变量的类是线程安全的
- 因为成员变量保存的数据也可以称为状态信息,因此没有成员变量就称之为【无状态】
21. 自定义线程池
-
-
package com.sunyang.concurrentstudy;import lombok.extern.slf4j.Slf4j;import java.util.ArrayDeque;import java.util.Deque;import java.util.HashSet;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.ReentrantLock;/** * @program: ConcurrentStudy * @description: Demo * @author: SunYang * @create: 2021-08-08 20:40 **/@Slf4j(topic = "c.Demo")public class MyPoolDemo { public static void main(String[] args) { // 任务数多于线程数,但小于任务队列大小。// ThreadPool threadPool = new ThreadPool(2, 1000, TimeUnit.MILLISECONDS, 10);// for (int i = 0; i < 5; i++) {// final int j = i;// threadPool.execute(() -> {// log.debug("{}", j);// });// } // // 任务数多于任务队列大小 ThreadPool threadPool = new ThreadPool(1, 1000, TimeUnit.MILLISECONDS, 1, (queue, task) -> { // 1. 死等// queue.put(task); // 2. 带超时等待// queue.offer(task, 500, TimeUnit.MILLISECONDS); // 3. 放弃任务执行// log.debug("放弃{}", task); // 4. 抛出异常// throw new RuntimeException("任务执行失败" + task); // 5. 让调用者自己执行任务 task.run(); }); for (int i = 0; i < 3; i++) { final int j = i; threadPool.execute(() -> { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("{}", j); }); } }}// 拒绝策略@FunctionalInterfaceinterface RejectPolicy<T> { void reject(BlockingQueue<T> queue, T task);}@Slf4j(topic = "c.Demo")class ThreadPool { // 任务队列 private BlockingQueue<Runnable> taskQueue; // 线程集合 private HashSet<Worker> workers = new HashSet<>(); // 核心线程数量 private int coreSize; // 获取任务的超时时间 private long timeout; // 时间单位 private TimeUnit timeUnit; private RejectPolicy<Runnable> rejectPolicy; // 执行任务 public void execute(Runnable task) { // 当任务数没有超过线程数,直接交给worker对象执行 // 如果任务数超过了线程数时,加入到任务队列暂存。 synchronized (workers){ if (workers.size() < coreSize) { Worker worker = new Worker(task); log.debug("新增worker{},{}", worker, task); workers.add(worker); worker.start(); } else {// taskQueue.put(task); // 1. 死等 // 2. 带超时等待 // 3. 放弃任务执行 // 4. 抛出异常 // 5. 让调用者自己执行任务 taskQueue.tryPut(rejectPolicy, task); } } } public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapacity, RejectPolicy<Runnable> rejectPolicy) { this.coreSize = coreSize; this.timeout = timeout; this.timeUnit = timeUnit; this.taskQueue = new BlockingQueue<>(queueCapacity); this.rejectPolicy = rejectPolicy; } class Worker extends Thread{ private Runnable task; public Worker (Runnable task) { this.task = task; } @Override public void run() { // 执行任务 // 1. 当task不为空,执行任务 // 2. 当task执行完毕,再接着从任务队列获取任务,并执行// while(task != null || (task = taskQueue.take()) != null) { while(task != null || (task = taskQueue.poll(timeout, timeUnit)) != null) { try { log.debug("正在执行...{}", task); task.run(); } catch (Exception e) { e.printStackTrace(); } finally { task = null; } } synchronized (workers){ log.debug("worker被移除...{}", this); workers.remove(this); } } }}@Slf4j(topic = "c.Demo")class BlockingQueue<T> { // 1. 任务队列 private Deque<T> queue = new ArrayDeque<>(); // 2. 锁 用来防止多个线程抢到同一个任务。 private ReentrantLock lock = new ReentrantLock(); // 3. 生产者条件变量 private Condition fullWaitSet = lock.newCondition(); // 4. 消费者条件变量 private Condition emptyWaitSet = lock.newCondition(); // 5. 容量 private int capacity; public BlockingQueue(int capacity) { this.capacity = capacity; } // 带超时的阻塞获取 public T poll(long timeout, TimeUnit unit) { lock.lock(); try { // 将timeout 统一转换成纳秒 long nanos = unit.toNanos(timeout); while (queue.isEmpty()) { try { // 返回剩余时间 if ((nanos <=0)) { return null; } nanos = emptyWaitSet.awaitNanos(nanos); } catch (InterruptedException e) { e.printStackTrace(); } } T t = queue.removeFirst(); fullWaitSet.signal(); return t; } finally { lock.unlock(); } } // 阻塞获取 public T take() { lock.lock(); try { while (queue.isEmpty()) { try { emptyWaitSet.await(); } catch (InterruptedException e) { e.printStackTrace(); } } T t = queue.removeFirst(); fullWaitSet.signal(); return t; } finally { lock.unlock(); } } // 阻塞添加 public void put(T task) { lock.lock(); try { while (queue.size() == capacity){ try { log.debug("等待加入任务队列{}。。。 ", task); fullWaitSet.await(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("加入任务队列{} ", task); queue.addLast(task); emptyWaitSet.signal(); } finally { lock.unlock(); } } // 带超时时间的阻塞添加 public boolean offer(T task, long timeout, TimeUnit timeUnit) { lock.lock(); try { long nanos = timeUnit.toNanos(timeout); while (queue.size() == capacity){ try { if (nanos < 0) { return false; } log.debug("等待加入任务队列{}。。。 ", task); nanos = fullWaitSet.awaitNanos(nanos); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("加入任务队列{} ", task); queue.addLast(task); emptyWaitSet.signal(); return true; } finally { lock.unlock(); } } // 获取大小 public int size() { lock.lock(); try { return queue.size(); } finally { lock.unlock(); } } public void tryPut(RejectPolicy<T> rejectPolicy, T task) { lock.lock(); try { // 判断队列是否满了 if (queue.size() == capacity) { rejectPolicy.reject(this, task); } else { // 队列还有空闲 log.debug("加入任务队列{}", task); queue.addLast(task); emptyWaitSet.signal(); } } finally { lock.unlock(); } }}
22. 线程池
(ThreadPoolExecutor)
22.1 线程池状态
-
ThreadPoolExecutor使用int的高三位来表示线程池状态, 低29位表示线程数量
-
状态名 高三位 接收新任务 处理阻塞队列任务 说明 RUNNING 111 Y Y 当线程池被创建出来就是RUNNING状态,可以接收新任务,也可以处理阻塞队列中的任务。 SHUTDOWN 000 N Y SHUTDOWN后不会接收新任务,但是会将已提交的任务和阻塞队列中的剩余任务执行完。 STOP 001 N N 会中断正在执行的任务,并抛弃阻塞队列中的任务 TIDYING 010 - - 任务全执行完毕,活动线程位0,即将进入终结状态 TERMINATED 011 - - 终结状态 -
从数字上比较,TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING(最高位为正负数标记位)
-
为什么用一个整数表示线程池状态信息和线程数量
- 因为将这些信息存储在一个原子变量ctl中,目的是将线程池状态与线程池个数合二为一,这样就可以用一次CAS原子操作进行赋值。
22.2 构造方法
-
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
-
corePoolSize 核心线程数目(最多保留的线程数)
-
maximunPoolSize 最大线程数目
-
keepAliveTime 生存时间-针对救急线程
-
unit 时间单位-针对救急线程
-
workQueue 阻塞队列
-
threadFactory 线程工厂- 可以为线程创建时起一个好名字。
-
handler 阻塞策略
22.2.1 工作方式:
-
-
当线程池开始被创建时会创建两个核心线程,核心线程1和核心线程2,当任务三来的时候会进入阻塞队列等待,当阻塞队列满了时,当任务5再来的时候,发现阻塞队列也满了,这时就会创建一个救急线程1,然后让救急线程一去执行任务5,等到任务5执行完毕,且没有超出阻塞队列的线程时,就会根据生存时间keepAliveTime,然后销毁,也就是没有任务多久后消亡,下次应急再创建,核心线程不会消亡,执行完任务,即使没有任务,也不会消亡。
22.2.2 工作流程
-
线程池中刚开始没有线程,当一个任务交给线程池后,线程池会创建一个新线程来执行任务
-
当线程数达到 corePoolSize 并没有线程空闲,这时再加入任务,新加的任务会被加入workQueue 队列排 队,直到有空闲的线程。
-
如果队列选择了有界队列,那么任务超过了队列大小时,会创建 maximumPoolSize - corePoolSize 数目的线 程来救急。
-
如果线程数达到了maximumPoolSize仍然有新任务这时会执行拒绝策略,JDK提供了四种拒绝策略的实现,其他著名框架也提供了实现
-
AbortPolicy (默认拒绝策略) 让调用者抛出RejectedExecutionException异常,这是默认的策略模式
-
**CallerRunPolicy ** 让调用者自己执行任务
-
DiscardPolicy 放弃本次任务
-
DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
-
-
Dubbo的实现,再抛出RejectedExecutionException 异常之前会记录日志,并dump线程栈信息,方便定位问题
-
Netty的实现, 是创建一个新线程来执行任务,(感觉不太好,因为设置线程池和线程数的其中一个原因是因为想限制线程数量,这样就达不到限制线程数量的初衷。)
-
ActiveMQ的实现,带超时等待(60s)尝试放入队列,
-
PinPoint 的实现,它使用了一个拒绝策略链,会逐一尝试策略链中每种拒绝策略
-
当高峰过去后,超过corePoolSize 的救急线程如果一段时间没有任务做,需要结束节省资源,这个时间由 keepAliveTime 和 unit 来控制.
-
-
根据这个构造方法,JDK Executors 类中提供了众多工厂方法来创建各种用途的线程池
22.3 newFixedThreadPool
- (固定大小的线程池)
22.3.1 源码
-
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());}
22.3.2 特点
- 核心线程数 == 最大线程数(没有救急线程被创建),因此也没有超时时间
- 阻塞队列是无界的(LinkedBlockingQueue无界阻塞队列,默认大小int最大值),可以放任意数量的任务
22.3.3 适用场景
-
适用于任务量已知,相对耗时的任务。
-
package com.sunyang.concurrentstudy;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * @program: ConcurrentStudy * @description: Demo * @author: SunYang * @create: 2021-08-09 16:44 **/@Slf4j(topic = "c.Demo")public class FixedThreadPoolDemo { public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(2); pool.execute(() -> { log.debug("1"); }); pool.execute(() -> { log.debug("2"); }); pool.execute(() -> { log.debug("3"); }); }}
-
16:54:58 [pool-1-thread-1] c.Demo - 116:54:58 [pool-1-thread-2] c.Demo - 216:54:58 [pool-1-thread-2] c.Demo - 3
22.4 newCachedThreadPool
- 带缓存功能的线程池
22.4.1 源码
-
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); // 同步队列 就只是一个交易地点,没有买方的时候,卖方进来也没用,这个是没有买方根本进不来, 也就是有消费者线程来取了,任务才能放到队列中,容量为0,不存储任务。}
22.4.2 特点
-
核心线程数是0,最大线程数是Integer.MAX_VALUE, 也就是全是救急线程,救急线程的空闲生存时间是60s,意味着
- 全部是救急线程(60s)后被回收掉。
- 救急线程类似于可以无限创建(最大线程数是Integer.MAX_VALUE)
-
因为缓存线程池就是你来一个任务我就创建一个线程(如果没有空闲线程可用),所以这个队列就无需存储任务。
-
队列采用的是SynchronousQueue实现特点是,他没有容量,没有线程来取是放不进去的(一手交钱,一手交货)
-
SynchronousQueue
- 同步队列 就只是一个交易地点,没有买方的时候,卖方进来也没用,这个是没有买方根本进不来,
- 也就是有消费者线程来取了,任务才能放到队列中,容量为0,不存储任务。
22.4.3 适用场景
-
整个线程池表现为线程数会根据业务量不断增长,没有上限,当任务执行完毕,空闲60s后会释放线程。
-
适合任务数比较密集,但每个任务执行时间比较短的情况。
-
package com.sunyang.concurrentstudy;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.SynchronousQueue;/** * @program: ConcurrentStudy * @description: Demo * @author: SunYang * @create: 2021-08-09 17:15 **/@Slf4j(topic = "c.Demo")public class CachedThreadPoolDemo { public static void main(String[] args) { ExecutorService pool = Executors.newCachedThreadPool(); pool.execute(() -> { log.debug("1"); }); pool.execute(() -> { log.debug("2"); }); pool.execute(() -> { log.debug("3"); }); pool.execute(() -> { log.debug("4"); }); }}
-
17:17:59 [pool-1-thread-1] c.Demo - 117:17:59 [pool-1-thread-4] c.Demo - 417:17:59 [pool-1-thread-3] c.Demo - 317:17:59 [pool-1-thread-2] c.Demo - 2Process finished with exit code 0 // 没有任务60s后线程自动结束
-
package com.sunyang.concurrentstudy;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.SynchronousQueue;import java.util.concurrent.TimeUnit;/** * @program: ConcurrentStudy * @description: Demo * @author: SunYang * @create: 2021-08-09 17:15 **/@Slf4j(topic = "c.Demo")public class CachedThreadPoolDemo { public static void main(String[] args) throws InterruptedException { SynchronousQueue<Integer> integers = new SynchronousQueue<>(); new Thread(() -> { try { log.debug("putting {}", 1); integers.put(1); log.debug("putted {}", 1); log.debug("putting {}", 2); integers.put(2); log.debug("putted {}", 2); } catch (InterruptedException e) { e.printStackTrace(); } }, "t1").start(); new Thread(() -> { try { log.debug("taking {}", 1); integers.take(); } catch (InterruptedException e) { e.printStackTrace(); } }, "t2").start(); TimeUnit.SECONDS.sleep(1); new Thread(() -> { try { log.debug("taking {}", 2); integers.take(); } catch (InterruptedException e) { e.printStackTrace(); } }, "t3").start(); }}
-
17:23:18 [t1] c.Demo - putting 117:23:18 [t2] c.Demo - taking 117:23:18 [t1] c.Demo - putted 117:23:18 [t1] c.Demo - putting 217:23:19 [t3] c.Demo - taking 217:23:19 [t1] c.Demo - putted 2Process finished with exit code 0
22.5 newSingleThreadExecutor
- (单线程的线程池)
22.5.1 源码
-
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));}
22.5.2 特点
-
自己创建一个单线程串行执行任务,如果任务执行失败而终止,那么没有任何补救措施,而线程池还会创建一个线程,保证池的正常工作。
-
package com.sunyang.concurrentstudy;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * @program: ConcurrentStudy * @description: Demo * @author: SunYang * @create: 2021-08-09 19:14 **/@Slf4j(topic = "c.Demo")public class SingleThreadPoolDmeo { public static void main(String[] args) { ExecutorService pool = Executors.newSingleThreadExecutor(); pool.execute(() -> { log.debug("1"); int i = 1 / 0; }); pool.execute(() -> { log.debug("2"); }); pool.execute(() -> { log.debug("3"); }); }}
-
19:17:44 [pool-1-thread-1] c.Demo - 119:17:44 [pool-1-thread-2] c.Demo - 219:17:44 [pool-1-thread-2] c.Demo - 3Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero at com.sunyang.concurrentstudy.SingleThreadPoolDmeo.lambda$main$0(SingleThreadPoolDmeo.java:20) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748)
-
-
Executors.newSingleThreadExecutors()线程个数始终为1,不能修改。
- FinalizableDelegatedExecutorService应用的是装饰器模式,只对外暴漏了ExecutorService接口,因此不能调用ThreadPoolExecutor中特有的方法
-
Executors.newFixedThreadPool(1)初始时为1,以后还可以更改
- 对外暴漏dhiThreadPoolExecutor对象,可以强转后调用setCorePoolSize等方法进行修改。
22.5.3 适用场景
- 希望多个任务排队进行,线程数固定为1,任务数多余1时,会放入无界队列排队,任务执行完毕,这唯一的线程也不会释放。
22.6 提交任务
-
// 执行任务void execute(Runnable command);// 提交任务task,用返回值Future获得任务执行结果<T> Future<T> submit(Callable<T> task); // 提交tasks中所有任务<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;// 提交tasks中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其他任务取消<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;// 提交tasks中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其他任务取消 带超时时间<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
-
submit()示例
-
package com.sunyang.concurrentstudy;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.*;/** * @program: ConcurrentStudy * @description: Demo * @author: SunYang * @create: 2021-08-09 19:31 **/@Slf4j(topic = "c.Demo")public class FutureSubmitCallableDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService pool = Executors.newFixedThreadPool(2); Future<String> future = pool.submit(() -> { log.debug("running.."); TimeUnit.SECONDS.sleep(1); return "ok"; }); log.debug("{}", future.get()); }}
-
19:35:25 [pool-1-thread-1] c.Demo - running..19:35:26 [main] c.Demo - ok
-
invokeAll() 示例
-
package com.sunyang.concurrentstudy;import lombok.extern.slf4j.Slf4j;import java.util.Arrays;import java.util.List;import java.util.concurrent.*;/** * @program: ConcurrentStudy * @description: Demo * @author: SunYang * @create: 2021-08-09 19:31 **/@Slf4j(topic = "c.Demo")public class FutureSubmitCallableDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService pool = Executors.newFixedThreadPool(2); List<Future<String>> futureList = pool.invokeAll(Arrays.asList( () -> { log.debug("1 begin"); TimeUnit.SECONDS.sleep(1); return "1"; }, () -> { log.debug("2 begin"); TimeUnit.SECONDS.sleep(2); return "2"; }, () -> { log.debug("3 begin"); TimeUnit.SECONDS.sleep(3); return "3"; } )); futureList.forEach(f -> { try { log.debug(f.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }); }}
-
19:50:43 [pool-1-thread-2] c.Demo - 2 begin19:50:43 [pool-1-thread-1] c.Demo - 1 begin19:50:44 [pool-1-thread-1] c.Demo - 3 begin19:50:47 [main] c.Demo - 119:50:47 [main] c.Demo - 219:50:47 [main] c.Demo - 3
-
-
invokeAny() 示例
-
package com.sunyang.concurrentstudy;import lombok.extern.slf4j.Slf4j;import java.util.Arrays;import java.util.List;import java.util.concurrent.*;/** * @program: ConcurrentStudy * @description: Demo * @author: SunYang * @create: 2021-08-09 19:31 **/@Slf4j(topic = "c.Demo")public class FutureSubmitCallableDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService pool = Executors.newFixedThreadPool(2); String str = pool.invokeAny(Arrays.asList( () -> { log.debug("1 begin"); TimeUnit.SECONDS.sleep(1); log.debug("1 end"); return "1"; }, () -> { log.debug("2 begin"); TimeUnit.SECONDS.sleep(2); log.debug("2 end"); return "2"; }, () -> { log.debug("3 begin"); TimeUnit.SECONDS.sleep(3); log.debug("3 end"); return "3"; } )); log.debug("{}", str); }}
-
19:56:09 [pool-1-thread-1] c.Demo - 1 begin19:56:09 [pool-1-thread-2] c.Demo - 2 begin19:56:10 [pool-1-thread-1] c.Demo - 1 end19:56:10 [main] c.Demo - 1
-
22.7 关闭线程池
22.7.1 shutdown()
-
/**线程池状态变为SHUTDOWN不会接收新任务但已提交的线程会执行完此方法不会阻塞调用线程的执行 **/void shutdown();
-
public void shutdown() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { checkShutdownAccess(); // 修改线程池状态 advanceRunState(SHUTDOWN); // 仅会打断空闲线程 interruptIdleWorkers(); onShutdown(); // 扩展点 ScheduledThreadPoolExecutor } finally { mainLock.unlock(); } // 尝试终结(没有运行的线程可以立刻终结) tryTerminate();}
22.7.2 shutdownNow()
-
/**尝试停止所有正在执行的任务,停止等待任务的处理,并返回等待执行的任务列表。 从该方法返回时,这些任务将从任务队列中排出(移除)。此方法不会等待主动执行的任务终止。 使用awaitTermination来做到这一点。除了尽力尝试停止处理正在执行的任务之外,没有任何保证。 此实现通过Thread.interrupt取消任务,因此任何未能响应中断的任务可能永远不会终止。抛出:SecurityException –**/public List<Runnable> shutdownNow() { List<Runnable> tasks; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { checkShutdownAccess(); // 修改线程池状态 advanceRunState(STOP); // 打断所有线程 interruptWorkers(); // 获取队列中剩余任务 tasks = drainQueue(); } finally { mainLock.unlock(); } // 尝试终结 tryTerminate(); return tasks;}
-
其他方法
-
// 不在 RUNNING 状态的线程池,此方法就返回 trueboolean isShutdown();// 线程池状态是否是 TERMINATEDboolean isTerminated();// 调用 shutdown 后,由于调用线程并不会等待所有任务运行结束,因此如果它想在线程池 TERMINATED 后做些事情,可以利用此方法等待boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
22.7.3 代码演示
-
package com.sunyang.concurrentstudy;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;import java.util.concurrent.TimeUnit;/** * @Author: sunyang * @Date: 2021/8/10 * @Description: */@Slf4j(topic = "c.Demo")public class ThreadPoolShutDownDemo { public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(2); // shutdown后已经提交的任务会继续执行 Future<Integer> result1 = pool.submit(() -> { log.debug("task 1 running...."); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("task 1 finish...."); return 1; }); // shutdown后已经提交的任务会继续执行 Future<Integer> result2 = pool.submit(() -> { log.debug("task 2 running...."); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("task 2 finish...."); return 2; }); // shutdown后任务队列中的任务也会被执行完毕 Future<Integer> result3 = pool.submit(() -> { log.debug("task 3 running...."); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("task 3 finish...."); return 3; }); log.debug("shutdown...."); pool.shutdown(); log.debug("调用完shutdown后, 该线程会继续向下运行,不会阻塞。。。"); // shutdown之后的任务不会被执行。 Future<Integer> result4 = pool.submit(() -> { log.debug("task 4 running...."); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("task 4 finish...."); return 4; }); }}
-
13:57:15 [main] c.Demo - shutdown....13:57:15 [pool-1-thread-2] c.Demo - task 2 running....13:57:15 [pool-1-thread-1] c.Demo - task 1 running....13:57:15 [main] c.Demo - 调用完shutdown后, 该线程会继续向下运行,不会阻塞。。。Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@6fffcba5 rejected from java.util.concurrent.ThreadPoolExecutor@34340fab[Shutting down, pool size = 2, active threads = 2, queued tasks = 1, completed tasks = 0] at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063) at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830) at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379) at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134) at com.sunyang.concurrentstudy.ThreadPoolShutDownDemo.main(ThreadPoolShutDownDemo.java:61)13:57:16 [pool-1-thread-2] c.Demo - task 2 finish....13:57:16 [pool-1-thread-1] c.Demo - task 1 finish....13:57:16 [pool-1-thread-1] c.Demo - task 3 running....13:57:17 [pool-1-thread-1] c.Demo - task 3 finish....
-
awaitTermination()
-
package com.sunyang.concurrentstudy; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; /** * @Author: sunyang * @Date: 2021/8/10 * @Description: */ @Slf4j(topic = "c.Demo") public class ThreadPoolShutDownDemo { public static void main(String[] args) throws InterruptedException { ExecutorService pool = Executors.newFixedThreadPool(2); // shutdown后已经提交的任务会继续执行 Future<Integer> result1 = pool.submit(() -> { log.debug("task 1 running...."); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("task 1 finish...."); return 1; }); // shutdown后已经提交的任务会继续执行 Future<Integer> result2 = pool.submit(() -> { log.debug("task 2 running...."); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("task 2 finish...."); return 2; }); // shutdown后任务队列中的任务也会被执行完毕 Future<Integer> result3 = pool.submit(() -> { log.debug("task 3 running...."); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("task 3 finish...."); return 3; }); log.debug("shutdown...."); pool.shutdown(); // 等够时间或者线程池中的任务执行完毕,才会继续运行。 pool.awaitTermination(3, TimeUnit.SECONDS); log.debug("调用完shutdown后, 该线程会继续向下运行,不会阻塞。。。"); // shutdown之后的任务不会被执行。 Future<Integer> result4 = pool.submit(() -> { log.debug("task 4 running...."); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("task 4 finish...."); return 4; }); } }
-
13:59:14 [main] c.Demo - shutdown.... 13:59:14 [pool-1-thread-2] c.Demo - task 2 running.... 13:59:14 [pool-1-thread-1] c.Demo - task 1 running.... 13:59:15 [pool-1-thread-1] c.Demo - task 1 finish.... 13:59:15 [pool-1-thread-2] c.Demo - task 2 finish.... 13:59:15 [pool-1-thread-1] c.Demo - task 3 running.... 13:59:16 [pool-1-thread-1] c.Demo - task 3 finish.... 13:59:16 [main] c.Demo - 调用完shutdown后, 该线程会继续向下运行,不会阻塞。。。 Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@4ae3c1cd rejected from java.util.concurrent.ThreadPoolExecutor@568bf312[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 3] at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063) at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830) at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379) at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134) at com.sunyang.concurrentstudy.ThreadPoolShutDownDemo.main(ThreadPoolShutDownDemo.java:63) Disconnected from the target VM, address: '127.0.0.1:56641', transport: 'socket'
-
-
shutdownNow()
-
package com.sunyang.concurrentstudy; import lombok.extern.slf4j.Slf4j; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; /** * @Author: sunyang * @Date: 2021/8/10 * @Description: */ @Slf4j(topic = "c.Demo") public class ThreadPoolShutDownDemo { public static void main(String[] args) throws InterruptedException { ExecutorService pool = Executors.newFixedThreadPool(2); // shutdownNow后已经提交的任务会被直接结束 Future<Integer> result1 = pool.submit(() -> { log.debug("task 1 running...."); TimeUnit.SECONDS.sleep(1); log.debug("task 1 finish...."); return 1; }); // shutdownNow后已经提交的任务会被直接结束 Future<Integer> result2 = pool.submit(() -> { log.debug("task 2 running...."); TimeUnit.SECONDS.sleep(1); log.debug("task 2 finish...."); return 2; }); // shutdown后任务队列中的任务会被返回 Future<Integer> result3 = pool.submit(() -> { log.debug("task 3 running...."); TimeUnit.SECONDS.sleep(1); log.debug("task 3 finish...."); return 3; }); log.debug("shutdown...."); List<Runnable> runnables = pool.shutdownNow(); log.debug("other..{}", runnables); } }
-
14:07:29 [main] c.Demo - shutdown....14:07:29 [pool-1-thread-2] c.Demo - task 2 running....14:07:29 [pool-1-thread-1] c.Demo - task 1 running....14:07:29 [main] c.Demo - other..[java.util.concurrent.FutureTask@1e67a849]Disconnected from the target VM, address: '127.0.0.1:57918', transport: 'socket'Process finished with exit code 0
-
23. 异步模式之工作线程
23.1 定义
-
让有限的工作线程(Worker Thread)来轮流异步处理无限多的任务,也可以将其归类为分工模式,他的典型实现就是线程池,也体现了经典设计模式中的享元模式。
-
例如:海底捞的服务员(线程), 轮流处理每位客人的点餐(任务),如果为每位客人都配一名专属的服务员,那么成本就太高了(对比另一种多线程设计模式:Thread Per - Message,这个模式就是来一个任务就创建一个线程,但是不复用) newCacheThreadPool虽然也是来一个任务创建一个线程,但是这个线程池中的线程是可以复用的,如果我执行完一个任务,还有下一个任务过来了,那么我还会继续执行。
-
注意:不同任务类型应该使用不同类型的线程池,这样能够避免饥饿,并能提高效率。
-
例如:如果一个餐馆的工人既要招呼客人(任务类型A)又要到后厨做菜(任务类型B)显然效率比较低,分成服务员(线程池A)与厨师(线程池B)更为合理。
23.2 饥饿(和之前的不同)
-
之前的是因为有线程,但是因为线程不能合理分配导致的饥饿问题,这个是因为没有线程可以用来执行任务了而导致的饥饿问题。
-
固定大小的线程池会有饥饿现象
- 两个工人是同一个线程池中的两个线程(全能工人)
- 他们要做的事情是:为客人点餐和到后厨做菜,这是两个阶段的工作
- 客人点餐:必须先点完餐,等菜做好,上菜,在此期间处理点餐的工人必须等待。
- 后厨做菜:就是干,做菜就完了。
- 比如工人A处理了点餐任务,接下来他要等着工人B把菜做好,然后上菜,他俩也配合的挺好。
- 但现在同时来了两个客人,两个工人都在给客人点餐,那么就没人做菜了。饥饿。(如果在多一个工人,那么另一个工人就看可以去做菜了)由于线程不足导致的饥饿问题。
23.2.1 饥饿代码示例
-
package com.sunyang.concurrentstudy;import lombok.extern.slf4j.Slf4j;import java.util.Arrays;import java.util.List;import java.util.Random;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;/** * @Author: sunyang * @Date: 2021/8/10 * @Description: */@Slf4j(topic = "c.Demo")public class ThreadStarvationDemo { static final List<String> MENU = Arrays.asList("锅包肉", "溜肉段", "宫保鸡丁", "烤鸡翅"); static Random RANDOM = new Random(); static String cooking() { return MENU.get(RANDOM.nextInt(MENU.size())); } public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(2); pool.execute(() -> { log.debug("处理点餐..... "); Future<String> future = pool.submit(() -> { log.debug("做菜"); return cooking(); }); try { log.debug("上菜:{}", future.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }); }}
-
15:06:33 [pool-1-thread-1] c.Demo - 处理点餐..... 15:06:33 [pool-1-thread-2] c.Demo - 做菜15:06:33 [pool-1-thread-1] c.Demo - 上菜:宫保鸡丁
-
package com.sunyang.concurrentstudy;import lombok.extern.slf4j.Slf4j;import java.util.Arrays;import java.util.List;import java.util.Random;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;/** * @Author: sunyang * @Date: 2021/8/10 * @Description: */@Slf4j(topic = "c.Demo")public class ThreadStarvationDemo { static final List<String> MENU = Arrays.asList("锅包肉", "溜肉段", "宫保鸡丁", "烤鸡翅"); static Random RANDOM = new Random(); static String cooking() { return MENU.get(RANDOM.nextInt(MENU.size())); } public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(2); pool.execute(() -> { log.debug("处理点餐..... "); Future<String> future = pool.submit(() -> { log.debug("做菜"); return cooking(); }); try { log.debug("上菜:{}", future.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }); pool.execute(() -> { log.debug("处理点餐..... "); Future<String> future = pool.submit(() -> { log.debug("做菜"); return cooking(); }); try { log.debug("上菜:{}", future.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }); }}
-
15:07:58 [pool-1-thread-2] c.Demo - 处理点餐..... 15:07:58 [pool-1-thread-1] c.Demo - 处理点餐.....
24.2.2 饥饿问题解决
-
package com.sunyang.concurrentstudy;import lombok.extern.slf4j.Slf4j;import java.util.Arrays;import java.util.List;import java.util.Random;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;/** * @Author: sunyang * @Date: 2021/8/10 * @Description: */@Slf4j(topic = "c.Demo")public class ThreadStarvationDemo { static final List<String> MENU = Arrays.asList("锅包肉", "溜肉段", "宫保鸡丁", "烤鸡翅"); static Random RANDOM = new Random(); static String cooking() { return MENU.get(RANDOM.nextInt(MENU.size())); } public static void main(String[] args) { ExecutorService waiterPool = Executors.newFixedThreadPool(2); ExecutorService cookPool = Executors.newFixedThreadPool(2); waiterPool.execute(() -> { log.debug("处理点餐..... "); Future<String> future = cookPool.submit(() -> { log.debug("做菜"); return cooking(); }); try { log.debug("上菜:{}", future.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }); waiterPool.execute(() -> { log.debug("处理点餐..... "); Future<String> future = cookPool.submit(() -> { log.debug("做菜"); return cooking(); }); try { log.debug("上菜:{}", future.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }); }}
-
15:27:19 [pool-1-thread-2] c.Demo - 处理点餐..... 15:27:19 [pool-1-thread-1] c.Demo - 处理点餐..... 15:27:19 [pool-2-thread-2] c.Demo - 做菜15:27:29 [pool-2-thread-1] c.Demo - 做菜15:27:29 [pool-1-thread-1] c.Demo - 上菜:宫保鸡丁15:27:29 [pool-1-thread-2] c.Demo - 上菜:宫保鸡丁
24. 创建多少线程合适
- 过小会导致程序不能充分的利用系统资源,容易导致饥饿
- 过大会导致更多的线程上下文切换,占用更多的内存。
24.1 CPU密集型运算
- 通常采用**
CPU核数 + 1
** 能够实现最优的CPU利用率,+1是保证当线程由于页缺失故障(操作系统,自行百度)或其他原因导致暂停时,额外的这个线程就能顶上去,保证CPU始终周期不被浪费。 - 简单点理解CPU密集型就是你的程序一直在做运算,不太需要读取数据。
24.2 I/O密集型运算
-
CPU不总是处于繁忙状态,例如:当你执行业务计算时,这时候会使用CPU资源,但当你执行I/O操作时,远程RPC调用时(网络IO),包括进行数据库操作时,这时候CPU就闲下来了,你就可以利用多线程提高他的利用率。
-
经验公式如下:
- 线 程 数 = 核 数 ∗ 期 望 C P U 利 用 率 ∗ ( 总 时 间 ( C P U 计 算 时 间 + 等 待 时 间 ) / C P U 计 算 时 间 ) 线程数 = 核数 * 期望CPU利用率 * (总时间(CPU计算时间+等待时间)/CPU计算时间) 线程数=核数∗期望CPU利用率∗(总时间(CPU计算时间+等待时间)/CPU计算时间)
-
例如:4核CPU(不算超线程的情况)计算时间是50%,其他等待时间(例如从内存读去所需数据)是50%,期望CPU被100%利用,套用公式
- 4 * 100% *100% / 50 % = 8;
-
例如:4核CPU计算时间是10%,其他等待时间是90%,期望CPU被100%利用,套用公式
- 4 * 100% *100% / 10 % = 40;
25. 任务调度线程池
- 在【任务调度线程池】功能加入之前,可以使用java.util.Timer来实现定时功能,TImer的优点在于简单易用,但由于所有任务都是由一个线程来调度,因此所有任务都是串行的,同一时间只能由一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务。
25.1 Timer(不推荐使用)
-
package com.sunyang.concurrentstudy;import lombok.SneakyThrows;import lombok.extern.slf4j.Slf4j;import java.util.Timer;import java.util.TimerTask;import java.util.concurrent.TimeUnit;/** * @Author: sunyang * @Date: 2021/8/10 * @Description: */@Slf4j(topic = "c.Demo")public class TimerDemo { public static void main(String[] args) { Timer timer = new Timer(); TimerTask task1 = new TimerTask() { @SneakyThrows @Override public void run() { log.debug("task 1"); TimeUnit.SECONDS.sleep(2); } }; TimerTask task2 = new TimerTask() { @Override public void run() { log.debug("task 2"); } }; // 使用 timer 添加两个任务,希望它们都在 1s 后执行 // 但由于 timer 内只有一个线程来顺序执行队列中的任务,因此『任务1』的延时,影响了『任务2』的执行 log.debug("start..."); timer.schedule(task1, 1000); timer.schedule(task2, 1000); }}
-
16:22:27 [main] c.Demo - start...16:22:28 [Timer-0] c.Demo - task 116:22:30 [Timer-0] c.Demo - task 2
25.2 ScheduledExecutorService
25.2.1 延时执行任务
-
package com.sunyang.concurrentstudy;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.ScheduledThreadPoolExecutor;import java.util.concurrent.ThreadFactory;import java.util.concurrent.TimeUnit;/** * @Author: sunyang * @Date: 2021/8/10 * @Description: */@Slf4j(topic = "c.Demo")public class ScheduledDemo { public static void main(String[] args) { ScheduledExecutorService pool = Executors.newScheduledThreadPool(2); pool.schedule(() -> { log.debug("task1 start"); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("task1 end"); }, 1, TimeUnit.SECONDS); pool.schedule(() -> { log.debug("task2 satrt"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("task2 end"); }, 1, TimeUnit.SECONDS); }}
-
16:46:46 [pool-1-thread-1] c.Demo - task1 start16:46:46 [pool-1-thread-2] c.Demo - task2 satrt16:46:47 [pool-1-thread-2] c.Demo - task2 end16:46:48 [pool-1-thread-1] c.Demo - task1 end
-
第一个任务出现异常不会影响任务二的执行
-
package com.sunyang.concurrentstudy;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.ScheduledThreadPoolExecutor;import java.util.concurrent.ThreadFactory;import java.util.concurrent.TimeUnit;/** * @Author: sunyang * @Date: 2021/8/10 * @Description: */@Slf4j(topic = "c.Demo")public class ScheduledDemo { public static void main(String[] args) { ScheduledExecutorService pool = Executors.newScheduledThreadPool(1); pool.schedule(() -> { log.debug("task1 start"); int i = 1 / 0;// try {// TimeUnit.SECONDS.sleep(2);// } catch (InterruptedException e) {// e.printStackTrace();// } log.debug("task1 end"); }, 1, TimeUnit.SECONDS); pool.schedule(() -> { log.debug("task2 satrt"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("task2 end"); }, 1, TimeUnit.SECONDS); }}
-
16:48:26 [pool-1-thread-1] c.Demo - task1 start16:48:26 [pool-1-thread-1] c.Demo - task2 satrt16:48:27 [pool-1-thread-1] c.Demo - task2 end
-
25.2.2 定时执行任务-周期
-
多长时间调用一次,但是是串行,如果上一个任务的执行时间超过了周期时间,则第二个任务要进行延时。
-
package com.sunyang.concurrentstudy;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.ScheduledThreadPoolExecutor;import java.util.concurrent.ThreadFactory;import java.util.concurrent.TimeUnit;/** * @Author: sunyang * @Date: 2021/8/10 * @Description: */@Slf4j(topic = "c.Demo")/**创建并执行一个周期性动作,在给定的初始延迟后首先启用,然后在给定的时间段内启用; 即执行将在initialDelay之后开始,然后是initialDelay+period ,然后是initialDelay + 2 * period ,依此类推。 如果任务的任何执行遇到异常,则后续执行将被抑制。 否则,任务只会通过取消或终止执行程序而终止。 如果此任务的任何执行时间超过其周期,则后续执行可能会延迟开始,但不会并发执行**/public class ScheduledDemo { public static void main(String[] args) { ScheduledExecutorService pool = Executors.newScheduledThreadPool(1); // 定时执行任务 log.debug("start..."); pool.scheduleAtFixedRate(() -> { log.debug("running...."); }, 1, 2, TimeUnit.SECONDS); }}
-
17:08:48 [main] c.Demo - start...17:08:49 [pool-1-thread-1] c.Demo - running....17:08:51 [pool-1-thread-1] c.Demo - running....17:08:53 [pool-1-thread-1] c.Demo - running....17:08:55 [pool-1-thread-1] c.Demo - running....17:08:57 [pool-1-thread-1] c.Demo - running....
26.2.3 定时执行任务-延时
-
每次执行任务的时间间隔,第一个任务执行完后多久执行下一个任务。
-
package com.sunyang.concurrentstudy;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.ScheduledThreadPoolExecutor;import java.util.concurrent.ThreadFactory;import java.util.concurrent.TimeUnit;/** * @Author: sunyang * @Date: 2021/8/10 * @Description: */@Slf4j(topic = "c.Demo")public class ScheduledDemo { public static void main(String[] args) { ScheduledExecutorService pool = Executors.newScheduledThreadPool(1); // 定时执行任务 log.debug("start..."); pool.scheduleWithFixedDelay(() -> { log.debug("running...."); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } }, 1, 1, TimeUnit.SECONDS); }}
-
17:13:43 [main] c.Demo - start...17:13:44 [pool-1-thread-1] c.Demo - running....17:13:47 [pool-1-thread-1] c.Demo - running....17:13:50 [pool-1-thread-1] c.Demo - running....17:13:53 [pool-1-thread-1] c.Demo - running....17:13:56 [pool-1-thread-1] c.Demo - running....17:13:59 [pool-1-thread-1] c.Demo - running....
26.2.4 异常处理
如果不做处理,正常是什么信息都不会打印的。所以必须自己对异常信息做处理。
-
手动try-catch
-
用Future的get() 方法获取。
-
package com.sunyang.concurrentstudy; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; /** * @Author: sunyang * @Date: 2021/8/10 * @Description: */ @Slf4j(topic = "c.Demo") public class ScheduledDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { ScheduledExecutorService pool = Executors.newScheduledThreadPool(1); // 正确处理异常。 ScheduledFuture<?> schedule = pool.schedule(() -> { log.debug("start...."); int i = 1 / 0; }, 1, TimeUnit.SECONDS); log.debug("{}", schedule.get()); } }
-
17:21:59 [pool-1-thread-1] c.Demo - start.... Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero at java.util.concurrent.FutureTask.report(FutureTask.java:122) at java.util.concurrent.FutureTask.get(FutureTask.java:192) at com.sunyang.concurrentstudy.ScheduledDemo.main(ScheduledDemo.java:28) Caused by: java.lang.ArithmeticException: / by zero at com.sunyang.concurrentstudy.ScheduledDemo.lambda$main$0(ScheduledDemo.java:25) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748)
26. Tomcat 线程池
26.1 组成
-
Tomcat在哪里用到了线程池
-
-
LimitLatch 用来限流,可以控制最大连接个数,类似于J.U.C中的Semaphore
-
Acceptor 只负责【接收新的socket连接】
-
Poller 只负责监听socket channel 是否有【可读的I/O事件】
-
一旦可读,封装一个任务对象【socketProcessor】,提交给Executor线程池处理
-
Executor线程池中的工作线程最终负责【处理请求】
Tomcat 线程池扩展了 ThreadPoolExecutor,行为稍有不同
- 如果总线程数达到 maximumPoolSize(和正常线程池不太一样,正常线程池是当任务队列满了以后且救急线程也都在执行任务才会执行拒绝策略。),
- 这时不会立刻抛 RejectedExecutionException 异常
- 而是再次尝试将任务放入队列,如果还失败,才抛出 RejectedExecutionException 异常
源码 tomcat-7.0.42
-
public void execute(Runnable command, long timeout, TimeUnit unit) { submittedCount.incrementAndGet(); try { super.execute(command); } catch (RejectedExecutionException rx) { if (super.getQueue() instanceof TaskQueue) { final TaskQueue queue = (TaskQueue)super.getQueue(); try { if (!queue.force(command, timeout, unit)) { // 尝试再次放入任务队列 submittedCount.decrementAndGet(); throw new RejectedExecutionException("Queue capacity is full."); } } catch (InterruptedException x) { submittedCount.decrementAndGet(); Thread.interrupted(); throw new RejectedExecutionException(x); } } else { submittedCount.decrementAndGet(); throw rx; } } }
-
public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException { if ( parent.isShutdown() ) throw new RejectedExecutionException( "Executor not running, can't force a command into the queue" ); return super.offer(o,timeout,unit); //forces the item onto the queue, to be used if the task is rejected }
26.1配置
-
Connector 配置
-
配置项 默认值 说明 acceptorThreadCount 1 acceptor 线程数量 pollerThreadCount 1 poller 线程数量 minSpareThreads 10 核心线程数,即 corePoolSize maxThreads 200 最大线程数,即 maximumPoolSize executor - Executor 名称,用来引用下面的 Executor如果配置了这个,则核心线程数和最大线程数以此配置为准 -
Executor 线程配置
-
配置项 默认值 说明 threadPriority 5 线程优先级 daemon true 是否守护线程 minSpareThreads 25 核心线程数,即 corePoolSize maxThreads 200 最大线程数,即 maximumPoolSize maxIdleTime 60000 线程生存时间,单位是毫秒,默认值即 1 分钟 maxQueueSize Integer.MAX_VALUE 队列长度 prestartminSpareThreads false 核心线程是否在服务器启动时启动
-
-
-
流程:
- 添加一个新任务,要判断已经提交的任务是否小于核心线程数
- 如果小于则加入任务队列中(因为线程池中的线程都是从任务队列中拿的,)所以先加入到任务队列中,然后新建一个核心线程从队列中拿到任务去执行
- 如果大于和核心线程数 ,则判断提交任务是否小于最大线程数
- 如果小于最大线程数则创建救急线程直接执行,不需要加入队列(和正常线程池不太一样,正常线程池是当任务队列满了以后才会创建救急线程。)
- 如果大于最大线程数,则加入到任务队列。
- 添加一个新任务,要判断已经提交的任务是否小于核心线程数
27. Fork/Join
27.1 概念
- Fork/Join 是 JDK 1.7 加入的新的线程池实现,它体现的是一种分治思想,适用于能够进行任务拆分的 cpu 密集型 运算
- 所谓的任务拆分,是将一个大任务拆分为算法上相同的小任务,直至不能拆分可以直接求解。跟递归相关的一些计 算,如归并排序、斐波那契数列、都可以用分治思想进行求解
- Fork/Join 在分治的基础上加入了多线程,可以把每个任务的分解和合并交给不同的线程来完成,进一步提升了运 算效率
- Fork/Join 默认会创建与 cpu 核心数大小相同的线程池
27.2 使用
-
package com.sunyang.concurrentstudy; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveTask; /** * @program: ConcurrentStudy * @description: Demo * @author: SunYang * @create: 2021-08-10 20:55 **/ @Slf4j(topic = "c.demo") public class ForkJoinDemo { public static void main(String[] args) { ForkJoinPool pool = new ForkJoinPool(4); System.out.println(pool.invoke(new AddTask1(5))); } } @Slf4j(topic = "c.demo") class AddTask1 extends RecursiveTask<Integer> { int n; public AddTask1(int n) { this.n = n; } @Override public String toString() { return "{" + n + '}'; } @Override protected Integer compute() { // 如果 n 已经为 1,可以求得结果了 if (n == 1) { log.debug("join() {}", n); return n; } // 将任务进行拆分(fork) AddTask1 t1 = new AddTask1(n - 1); t1.fork(); log.debug("fork() {} + {}", n, t1); // 合并(join)结果 int result = n + t1.join(); log.debug("join() {} + {} = {}", n, t1, result); return result; } }
-
20:57:31 [ForkJoinPool-1-worker-0] c.demo - fork() 2 + {1} 20:57:31 [ForkJoinPool-1-worker-2] c.demo - fork() 4 + {3} 20:57:31 [ForkJoinPool-1-worker-1] c.demo - fork() 5 + {4} 20:57:31 [ForkJoinPool-1-worker-3] c.demo - fork() 3 + {2} 20:57:31 [ForkJoinPool-1-worker-0] c.demo - join() 1 20:57:31 [ForkJoinPool-1-worker-0] c.demo - join() 2 + {1} = 3 20:57:31 [ForkJoinPool-1-worker-3] c.demo - join() 3 + {2} = 6 20:57:31 [ForkJoinPool-1-worker-2] c.demo - join() 4 + {3} = 10 20:57:31 [ForkJoinPool-1-worker-1] c.demo - join() 5 + {4} = 15 15
-
27.3 改进
-
package com.sunyang.concurrentstudy; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveTask; /** * @program: ConcurrentStudy * @description: Demo * @author: SunYang * @create: 2021-08-10 21:07 **/ @Slf4j(topic = "c.Demo") public class ForkJoinDemo2 { public static void main(String[] args) { ForkJoinPool pool = new ForkJoinPool(4); System.out.println(pool.invoke(new AddTask3(1, 10))); } } @Slf4j(topic = "c.Demo") class AddTask3 extends RecursiveTask<Integer> { int begin; int end; public AddTask3(int begin, int end) { this.begin = begin; this.end = end; } @Override public String toString() { return "{" + begin + "," + end + '}'; } @Override protected Integer compute() { // 5, 5 if (begin == end) { log.debug("join() {}", begin); return begin; } // 4, 5 if (end - begin == 1) { log.debug("join() {} + {} = {}", begin, end, end + begin); return end + begin; } // 1 5 int mid = (end + begin) / 2; // 3 AddTask3 t1 = new AddTask3(begin, mid); // 1,3 t1.fork(); AddTask3 t2 = new AddTask3(mid + 1, end); // 4,5 t2.fork(); log.debug("fork() {} + {} = ?", t1, t2); int result = t1.join() + t2.join(); log.debug("join() {} + {} = {}", t1, t2, result); return result; } }
-
21:12:42 [ForkJoinPool-1-worker-1] c.Demo - fork() {1,5} + {6,10} = ? 21:12:42 [ForkJoinPool-1-worker-1] c.Demo - join() 4 + 5 = 9 21:12:42 [ForkJoinPool-1-worker-0] c.Demo - fork() {1,2} + {3,3} = ? 21:12:42 [ForkJoinPool-1-worker-3] c.Demo - fork() {6,8} + {9,10} = ? 21:12:42 [ForkJoinPool-1-worker-2] c.Demo - fork() {1,3} + {4,5} = ? 21:12:42 [ForkJoinPool-1-worker-2] c.Demo - join() 1 + 2 = 3 21:12:42 [ForkJoinPool-1-worker-2] c.Demo - join() 3 21:12:42 [ForkJoinPool-1-worker-0] c.Demo - join() {1,2} + {3,3} = 6 21:12:42 [ForkJoinPool-1-worker-3] c.Demo - fork() {6,7} + {8,8} = ? 21:12:42 [ForkJoinPool-1-worker-3] c.Demo - join() 6 + 7 = 13 21:12:42 [ForkJoinPool-1-worker-3] c.Demo - join() 8 21:12:42 [ForkJoinPool-1-worker-3] c.Demo - join() {6,7} + {8,8} = 21 21:12:42 [ForkJoinPool-1-worker-2] c.Demo - join() {1,3} + {4,5} = 15 21:12:42 [ForkJoinPool-1-worker-0] c.Demo - join() 9 + 10 = 19 21:12:42 [ForkJoinPool-1-worker-3] c.Demo - join() {6,8} + {9,10} = 40 21:12:42 [ForkJoinPool-1-worker-1] c.Demo - join() {1,5} + {6,10} = 55 55
-
) 4 + 5 = 9
21:12:42 [ForkJoinPool-1-worker-0] c.Demo - fork() {1,2} + {3,3} = ?
21:12:42 [ForkJoinPool-1-worker-3] c.Demo - fork() {6,8} + {9,10} = ?
21:12:42 [ForkJoinPool-1-worker-2] c.Demo - fork() {1,3} + {4,5} = ?
21:12:42 [ForkJoinPool-1-worker-2] c.Demo - join() 1 + 2 = 3
21:12:42 [ForkJoinPool-1-worker-2] c.Demo - join() 3
21:12:42 [ForkJoinPool-1-worker-0] c.Demo - join() {1,2} + {3,3} = 6
21:12:42 [ForkJoinPool-1-worker-3] c.Demo - fork() {6,7} + {8,8} = ?
21:12:42 [ForkJoinPool-1-worker-3] c.Demo - join() 6 + 7 = 13
21:12:42 [ForkJoinPool-1-worker-3] c.Demo - join() 8
21:12:42 [ForkJoinPool-1-worker-3] c.Demo - join() {6,7} + {8,8} = 21
21:12:42 [ForkJoinPool-1-worker-2] c.Demo - join() {1,3} + {4,5} = 15
21:12:42 [ForkJoinPool-1-worker-0] c.Demo - join() 9 + 10 = 19
21:12:42 [ForkJoinPool-1-worker-3] c.Demo - join() {6,8} + {9,10} = 40
21:12:42 [ForkJoinPool-1-worker-1] c.Demo - join() {1,5} + {6,10} = 55
55
- [外链图片转存中...(img-lMbrd6QG-1631354299660)]