文章目录
一、连接池
模拟实现一个数据库连接池
public class ConnPool {
public static void main(String[] args) {
ConnPool connPool =new ConnPool(2);
for (int i = 0; i < 5; i++) {
new Thread(()->{
Connection connection = connPool.get();
try {
//随机睡眠
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
connPool.free(connection);
}
},"线程"+i).start();
}
}
//连接池大小
private final int poolSize;
//连接
private MyConnection[] connections;
//每个连接的状态,使用原子数组加cas确保线程安全
private AtomicIntegerArray status;
public ConnPool(int poolSize) {
this.poolSize = poolSize;
this.connections = new MyConnection[poolSize];
this.status = new AtomicIntegerArray(poolSize);
for (int i = 0; i < poolSize; i++) {
connections[i] = new MyConnection("连接"+i);
status.set(i, 0);
}
}
public Connection get() {
while (true) {
for (int i = 0; i < poolSize; i++) {
if (status.get(i) == 0) {
//status.set(i,1); set存在线程安全问题,改用cas
if (status.compareAndSet(i, 0, 1)) {
System.out.println(Thread.currentThread().getName()+"获得"+connections[i].getName());
return connections[i];
}
}
}
//没有空闲线程
synchronized (this) {
try {
System.out.println(Thread.currentThread().getName()+"等待...");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public void free(Connection connection) {
for (int i = 0; i < poolSize; i++) {
if (connections[i] == connection) {
//只有获取了连接的线程才能释放,不会发生竞争,因此没有线程安全问题
status.set(i, 0);
System.out.println(Thread.currentThread().getName()+"释放"+connections[i].getName());
break;
}
}
synchronized (this) {
this.notifyAll();
}
}
}
class MyConnection implements Connection {
private String name;
public MyConnection(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//实现方法省略
}
二、线程池
模拟实现一个线程池
public class TestThreadPool {
public static void main(String[] args) {
ThreadPool threadPool = new ThreadPool(2, 3, TimeUnit.SECONDS, 2);
for (int i = 0; i < 6; i++) {
int j = i;
threadPool.execute(() -> {
System.out.println("任务" + j);
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
class ThreadPool {
//核心线程数
// private final int coreSize;
//最大线程数
private final int maxSize;
//线程存活时间
private final long timeout;
//时间单位
private final TimeUnit unit;
//工作线程集合
private final Set<Worker> workers = new HashSet<>();
//任务队列
private final BlockingQueue<Runnable> blockingQueue;
//拒绝策略
private final RejectedHandler rejectedHandler;
public ThreadPool(int maxSize, long timeout, TimeUnit unit, int queueSize) {
this.maxSize = maxSize;
this.timeout = timeout;
this.unit = unit;
this.blockingQueue = new BlockingQueue<>(queueSize);
rejectedHandler = (Runnable task) -> {
// throw new RuntimeException();
// task.run();
};
}
public void execute(Runnable task) {
//有剩余线程,直接执行任务
synchronized (workers) {
if (workers.size() < maxSize) {
Worker worker = new Worker(task);
worker.start();
workers.add(worker);
} else {
//没有剩余线程加入任务队列
// blockingQueue.put(task);
if (!blockingQueue.offer(task, timeout, unit)) {
rejectedHandler.rejeced(task);
}
}
}
}
//工作线程
class Worker extends Thread {
private Runnable task;
public Worker(Runnable task) {
this.task = task;
}
@Override
public void run() {
//任务完成之后取队列任务继续执行
// while (task != null || (task = blockingQueue.take()) != null) {
while (task != null || (task = blockingQueue.poll(timeout, unit)) != null) {
System.out.println(this.getName());
task.run();
task = null;
}
//线程结束之后移除,否则将导致无法创建新的线程执行任务
synchronized (workers) {
workers.remove(this);
System.out.println(this+"被移除");
}
}
}
}
class BlockingQueue<T> {
//任务队列
private final Deque<T> deque = new ArrayDeque<>();
//队列最大数量
private final int dequeSize;
//锁
private final ReentrantLock lock = new ReentrantLock();
//队列已空
private final Condition emptyList = lock.newCondition();
//队列已满
private final Condition fullList = lock.newCondition();
public BlockingQueue(int dequeSize) {
this.dequeSize = dequeSize;
}
//阻塞获取一个任务
T take() {
lock.lock();
try {
while (deque.isEmpty()) {
//进入队列已空的条件等待
try {
emptyList.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = deque.removeFirst();
//唤醒队列已满的等待线程
fullList.signalAll();
return t;
} finally {
lock.unlock();
}
}
//获取一个任务带超时
T poll(long timeout, TimeUnit unit) {
long nanos = unit.toNanos(timeout);
lock.lock();
try {
while (deque.isEmpty()) {
//进入队列已空的条件等待
try {
//进入队列已空的条件等待,无剩余等待时间就返回
if (nanos <= 0) {
System.out.println(Thread.currentThread().getName() + "结束获取任务等待");
return null;
}
//返回剩余等待时间
nanos = emptyList.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = deque.removeFirst();
//唤醒队列已满的等待线程
fullList.signalAll();
return t;
} finally {
lock.unlock();
}
}
//阻塞添加一个任务
void put(T t) {
lock.lock();
try {
while (deque.size() == dequeSize) {
try {
fullList.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
deque.addLast(t);
emptyList.signalAll();
} finally {
lock.unlock();
}
}
//加入一个任务带超时
boolean offer(T t, long timeout, TimeUnit unit) {
long nanos = unit.toNanos(timeout);
lock.lock();
try {
while (deque.size() == dequeSize) {
try {
//进入队列已满的条件等待,无剩余等待时间就返回
if (nanos <= 0) {
System.out.println("结束添加任务等待" + t);
return false;
}
nanos = fullList.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
deque.addLast(t);
System.out.println("添加任务" + t);
emptyList.signalAll();
return true;
} finally {
lock.unlock();
}
}
int size() {
lock.lock();
try {
return deque.size();
} finally {
lock.unlock();
}
}
}
interface RejectedHandler {
void rejeced(Runnable task);
}
三、ThreadPoolExecutor
ThreadPoolExecutor使用一个int类型变量的高3位表示线程状态,后29位表示线程数量。
注意这里的高3位是补码,111表示的是-1。状态和数量在修改的时候需要使用cas操作保证原子性,合为一个数字之后如果同时更改,可以减少一次cas。而分别修改高低位,可以使用|运算将两个数字合并之后再用cas修改变量。
构造方法:
/**
* @param corePoolSize 核心线程数(最多保留的线程数)
* @param maximumPoolSize 最大线程数
* @param keepAliveTime 线程空闲时间-针对救急线程
* @param unit 时间单位-针对救急线程
* @param workQueue 工作阻塞队列
* @param threadFactory 线程工厂
* @param handler 拒绝策略
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
}
工作方式:
- 线程池中刚开始没有线程,当有任务提交时才创建线程,此时创建的是核心线程
- 任务太多,核心线程数目达到上限corePoolSize之后,任务会被放入阻塞队列workQueue,等待线程执行
- 如果workQueue是有界队列,随着任务继续增加,阻塞队列满了之后,就会创建救急线程。救急线程最大数目=maximumPoolSize-corePoolSize
- 如果线程数达到maximumPoolSize,任然有新的任务提交,就会执行拒绝策略
- 当任务高峰过去之后,超过corePoolSize数目的救急线程会结束,从而节省资源。救急线程的存活时间由keepAliveTime和unit控制
根据这个构造方法,JDK在Executors类中提供了几个静态工厂方法,来创建不同用途的线程池,不用我们指定线程池的参数
3.1 newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newFixedThreadPool是固定大小的线程池。这个线程池的核心线程数和最大线程数相同,即所有线程都是核心线程数,创建之后不会结束。LinkedBlockingQueue容量默认int最大值,新的任务会不断往里面添加。适应于任务量已知,任务时间较长的情况
public class newFixedThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2, r -> {
AtomicInteger i = new AtomicInteger(1);
return new Thread(r, "mypool_t" + i.getAndIncrement());
});
executorService.submit(() -> {
System.out.println(Thread.currentThread().getName() + "1");
});
executorService.submit(() -> {
System.out.println(Thread.currentThread().getName() + "2");
});
executorService.submit(() -> {
System.out.println(Thread.currentThread().getName() + "3");
});
}
}
任务执行完之后,程序仍然在运行,说明核心线程数在空转,线程池没有结束。
3.2 newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
newCachedThreadPool没有核心线程,与newFixedThreadPool相反,所有线程都是救急线程,最大数量不超过int最大值。意味着新任务提交时只要没有空闲线程就创建新线程。线程空闲存活时间为60s,增加了可复用性。SynchronousQueue是一个特殊队列,其本身是没有容量大小,放一个数据到队列中不能立刻返回的,必须等待放进去的数据被消费掉了,才能够返回。适用于任务密集,每个任务执行时间较短的情况
3.3 newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newSingleThreadExecutor是固定大小为1的线程池,它与newFixedThreadPool相似,只是数量固定是1不能修改,而newFixedThreadPool(1)可以修改大小。与自定义一个线程串行执行任务的区别是,当任务执行失败线程结束时,线程池会重新创建一个线程来执行后面的任务。适应于多个任务排队执行的情况
3.4 newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
newScheduledThreadPool是延时线程池,需要提供一个核心线程数。最大线程数为int最大值,也有线程过多的风险。DelayedWorkQueue的默认初始值为16,会自动扩容,最大值也是int最大值。适用于任务量已知且需要延迟执行的情况
public class newScheduledThreadPool {
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
System.out.println("begin");
pool.schedule(() -> System.out.println(Thread.currentThread().getName() + "1秒后打印"), 1, TimeUnit.SECONDS);
pool.schedule(() -> {
System.out.println(Thread.currentThread().getName() + "1秒后打印");
return "end";
}, 1, TimeUnit.SECONDS);
pool.execute(() -> System.out.println(Thread.currentThread().getName() + "execute"));
}
}
3.5 newWorkStealingPool
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool
(Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
newWorkStealingPool使用的是ForkJoinPool任务拆分合并线程池,线程数量固定为CPU核数,最大限度利用CPU进行密集运算。每个线程都有一个任务队列存放任务,当自己队列的任务执行完会窃取其他线程的队列任务,因此又叫抢占式线程池。适用于任务密集情况,每个任务执行时间长短均可
public class newWorkStealingPool {
public static void main(String[] args) {
//创建一个具有抢占式操作的线程池 1.8 之后新增 每个线程都有一个任务队列存放任务
ExecutorService pool = Executors.newWorkStealingPool(Runtime.getRuntime().availableProcessors());
LinkedBlockingDeque<Future<String>> strings = new LinkedBlockingDeque<>();
// CPU 核数
System.out.println(Runtime.getRuntime().availableProcessors());
for (int i = 0; i < Runtime.getRuntime().availableProcessors() + 1; i++) {
Future<String> submit = pool.submit(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "展示线程:" + Thread.currentThread().getName();
});
strings.offer(submit);
}
pool.shutdown();
strings.forEach(f -> {
try {
System.out.println(f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
}
}
四、线程池应用
4.1 多个线程池分工
public class DivisionThreadPool {
static List<String> list = Arrays.asList("辣子鸡", "水煮鱼", "粉蒸肉");
public static void main(String[] args) {
// ExecutorService pool = Executors.newFixedThreadPool(2);
//使用两个线程池分工
ExecutorService waiterPool = Executors.newFixedThreadPool(1);
ExecutorService cookPool = Executors.newFixedThreadPool(1);
waiterPool.execute(() -> {
System.out.println("点菜");
Future<String> submit = cookPool.submit(() -> cooking());
try {
System.out.println("上菜" + submit.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
waiterPool.execute(() -> {
System.out.println("点菜");
Future<String> submit = cookPool.submit(() -> cooking());
try {
System.out.println("上菜" + submit.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
}
private static String cooking() {
return list.get(new Random().nextInt(3));
}
}
4.2 正确处理线程池异常
执行任务时发生异常的处理
public class ThreadPoolException {
public static void main(String[] args){
ExecutorService pool = Executors.newCachedThreadPool();
pool.execute(() -> {
System.out.println("test");
//任务自己处理异常
try {
int i = 1 / 0;
System.out.println(i);
} catch (Exception e) {
e.printStackTrace();
}
});
Future<Boolean> future = pool.submit(() -> {
System.out.println("test");
int i = 1 / 0;
System.out.println(i);
return true;
});
//使用future获取异常
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
4.3 定时任务
使用newScheduledThreadPool实现每周三20点执行任务
public class ScheduledThreadPoolTimer {
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
long period = 24 * 60 * 60 * 1000 * 7;
LocalDateTime now = LocalDateTime.now();
LocalDateTime nextTime = now.withHour(20).withMinute(0).withSecond(0).withNano(0).with(DayOfWeek.THURSDAY);
System.out.println(nextTime);
long initialDelay = Duration.between(now, nextTime).toMillis();
if (now.isAfter(nextTime)) {
initialDelay = period - initialDelay;
}
/**
* 第一次等待initialDelay后开始执行,第二次在initialDelay+period开始执行
* 如果第一次>period,那么第二次开始时间为initialDelay+exetime
* 意思是后一次执行必须等待前一次执行完
* 类似方法scheduleWithFixedDelay需再上一次任务执行完之后等待period
* 第一次开始时间为initialDelay,执行时间为exetime,第二次开始时间为initialDelay+exetime+period
*/
pool.scheduleAtFixedRate(() -> {
System.out.println("每周三20点执行任务");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, initialDelay, period, TimeUnit.MICROSECONDS);
}
}