JUC学习-线程池部分
自定义线程池
package com.appletree24; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; class Main { public static void main(String[] args) throws ExecutionException, InterruptedException { ThreadPool threadPool = new ThreadPool(2, 1000, TimeUnit.MICROSECONDS, 5, (queue, task) -> { //带超时等待 // queue.offer(task,500,TimeUnit.MILLISECONDS); }); for (int i = 0; i < 10; i++) { int j = i; threadPool.execute(() -> { try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(j); }); } } } //策略模式接口 此处使用策略模式是因为在实现拒绝策略时,有许多种拒绝的方式,这些方式如果不使用恰当的模式,就需要大量的if..else来编写 //且方式数量大于4个,会造成类膨胀的问题,推荐使用混合模式 //https://www.runoob.com/design-pattern/strategy-pattern.html @FunctionalInterface interface RejectPolicy<T> { void reject(BlockingQueue<T> queue, T task); } 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); workers.add(worker); worker.start(); } else { //第一种选择死等 // taskQueue.put(task); //第二种为超时等待 //第三种为消费者放弃任务执行 //第四种为主线程抛出异常 //第五种让调用者自行执行任务 taskQueue.tryPut(rejectPolicy, task); } } } public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapcity, RejectPolicy<Runnable> rejectPolicy) { this.coreSize = coreSize; this.timeout = timeout; this.timeUnit = timeUnit; this.taskQueue = new BlockingQueue<>(queueCapcity); 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.poll(1000, TimeUnit.MILLISECONDS)) != null) { try { task.run(); } catch (Exception e) { e.printStackTrace(); } finally { task = null; } } synchronized (workers) { workers.remove(this); } } } } class BlockingQueue<T> { //1. 任务队列 private final Deque<T> queue = new ArrayDeque<>(); //2. 锁 private final ReentrantLock lock = new ReentrantLock(); //3. 生产者条件变量 private final Condition fullWaitSet = lock.newCondition(); //4. 消费者条件变量 private final Condition emptyWaitSet = lock.newCondition(); //5. 容量上限 private int capcity; public BlockingQueue(int capcity) { this.capcity = capcity; } //带超时的等待获取 public T poll(long timeout, TimeUnit unit) { lock.lock(); long nanos = unit.toNanos(timeout); try { 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() == capcity) { try { fullWaitSet.await(); } catch (InterruptedException e) { e.printStackTrace(); } } queue.offerLast(task); //添加完后唤醒消费者等待 emptyWaitSet.signal(); } finally { lock.unlock(); } } //带超时时间的阻塞添加 public boolean offer(T task, long timeout, TimeUnit unit) { lock.lock(); try { long nanos = unit.toNanos(timeout); while (queue.size() == capcity) { try { if (nanos <= 0) return false; nanos = fullWaitSet.awaitNanos(nanos); } catch (InterruptedException e) { e.printStackTrace(); } } queue.offerLast(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() == capcity) { rejectPolicy.reject(this, task); } else { queue.addLast(task); emptyWaitSet.signal(); } } finally { lock.unlock(); } } }
上述的自定义线程池虽然能够执行完毕主线程给予的任务,但任务全部执行结束后,开辟的线程池内核心线程仍然在运行,并没有结束,这是因为目前线程池中的take方法仍然为不会有超时等待的take方法,造成了死等,需要为其加入超时停止的功能。也就是替代take()的poll()
JDK自带线程池
介绍
ThreadPoolExecutor使用int的高三位表示线程池状态,低29位表示线程数量
在ThreadPoolExecutor中,同样也存在拒绝策略。其图结构如下:
其中接口就对应着在自定义线程池中实现的策略模式接口,下面的四个实现类就对应着四种不同的拒绝方式:
利用工具类创建固定大小线程池
利用工具类创建带缓冲的线程池
从源码可以看出,带缓冲的线程池中缓冲队列的使用的是一个名为SynchronousQueue
的队列,这个队列的特点如下:队列不具有容量,当没有线程来取时,是无法对其内部放入数据的,例如队列内部已有一个数字1,但此时没有线程取走,则线此队列目前并不能继续存入数据,直到1被取走
利用工具类创建单线程线程池
从源码可以看出,单线程线程池中核心线程数与最大线程数相等,即不存在应急线程。只能解决一个任务
那么这个线程池和我自己创建一个线程的线程池有什么区别呢?区别如下:
ThreadPoolExecutor-submit method
public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService pool = Executors.newFixedThreadPool(2); Future<String> result = pool.submit(() -> { System.out.println("running"); Thread.sleep(1000); return "ok"; }); System.out.println(result.get()); }
submit方法可以传入Runnable和Callable类型的参数,并且将线程内部所执行任务的结果返回,用Future包装
ThreadPoolExecutor-invokeAll
public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService pool = Executors.newFixedThreadPool(2); List<Future<String>> results = pool.invokeAll(Arrays.asList(() -> { System.out.println("begin"); Thread.sleep(1000); return "1"; }, () -> { System.out.println("begin"); Thread.sleep(500); return "2"; })); results.forEach(f -> { try { System.out.printf(f.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }); }
invokeAll方法可以传入任务的集合,同样的任务的返回值也会以列表形式返回
ThreadPoolExecutor-invokeAny
public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService pool = Executors.newFixedThreadPool(2); String result = pool.invokeAny(Arrays.asList(() -> { System.out.println("begin"); Thread.sleep(1000); return "1"; }, () -> { System.out.println("begin"); Thread.sleep(500); return "2"; })); pool.awaitTermination(1000, TimeUnit.MILLISECONDS); System.out.println(result); }
invokeAny方法同样可以传入任务的集合,只不过最后返回的结果并不是任务的结果集合,而是最早完成的那个任务的结果。
ThreadPoolExecutor-shutdown
public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService pool = Executors.newFixedThreadPool(2); List<Future<String>> results = pool.invokeAll(Arrays.asList(() -> { System.out.println("begin"); Thread.sleep(1000); return "1"; }, () -> { System.out.println("begin"); Thread.sleep(500); return "2"; })); pool.shutdown(); results.forEach(f -> { try { System.out.println(f.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }); }
shutdown方法会将线程池的状态变为SHUTDOWN
- 不会接受新任务
- 但已提交的任务会执行完
- 此方法不会阻塞调用线程的执行
ThreadPoolExecutor-shutdownNow
public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService pool = Executors.newFixedThreadPool(2); List<Future<String>> results = pool.invokeAll(Arrays.asList(() -> { System.out.println("begin"); Thread.sleep(1000); return "1"; }, () -> { System.out.println("begin"); Thread.sleep(500); return "2"; })); List<Runnable> runnables = pool.shutdownNow(); results.forEach(f -> { try { System.out.println(f.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }); }
shutdownNow方法会将线程池状态变为STOP
- 不会接受新任务
- 会将队列中现有的任务返回
- 并且用interrupt方法中断正在执行的任务
任务调度线程池
在任务调度线程池加入之前,JDK1.3有一个Timer的工具类可以实现定时功能,但是Timer存在很多缺点,例如Timer的所有定时任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都会影响之后的任务。
public static void main(String[] args) { ScheduledExecutorService pool = Executors.newScheduledThreadPool(2); pool.schedule(new Runnable() { @Override public void run() { System.out.println("1"); } }, 1, TimeUnit.SECONDS); pool.schedule(new Runnable() { @Override public void run() { System.out.println("2"); } }, 1, TimeUnit.SECONDS); }
上述的两个任务都会在执行完后1s再执行下一个。其实就相当于将Timer中的单线程特点进行了优化,可以用池的形式分配多个线程
public static void main(String[] args) { ScheduledExecutorService pool = Executors.newScheduledThreadPool(2); pool.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println("1"); } }, 1, 1, TimeUnit.SECONDS); }
上述任务会每隔1s执行一次,而执行开始的时间则是在被创建完后的1s。但是有个问题就在于,如果任务内部延时大于设定的间隔时间,并且延时大于间隔时间,那么就会等待内部延时结束才进行下一个任务的操作,总的耗时就是任务内的间隔时间
public static void main(String[] args) { ScheduledExecutorService pool = Executors.newScheduledThreadPool(2); pool.scheduleWithFixedDelay(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("1"); }, 1, 1, TimeUnit.SECONDS); }
而上述的方法则是会在内部任务完成后才开始进行任务间隔时间的计算,例如上述代码中最后两个任务的执行时间会是3s
处理线程池中的异常
在线程池中执行的任务,如果执行过程中发生了异常,并不会提示错误信息,如果想要进行异常处理。可以有两种方式
第一种方式是使用Callable类型的参数,通过返回值类型Future进行错误信息的打印。
第二种方式就是主动处理异常,在任务代码段中使用try-catch进行异常捕获,最终输出
public static void main(String[] args) throws ExecutionException, InterruptedException { ScheduledExecutorService pool = Executors.newScheduledThreadPool(2); Future<Boolean> result = pool.submit(() -> { int a = 1 / 0; return true; }); // System.out.println(result.get()); }
如果采取上面的编写方式,就算内部出现了数值溢出,终端内也不会有任何提示。
当把被注释掉的代码放开后,再次运行就可以顺利看到内部的报错了。
定时执行任务
public static void main(String[] args) throws ExecutionException, InterruptedException { ScheduledExecutorService pool = Executors.newScheduledThreadPool(2); //在每周四18:00定时执行任务 long period = 1000 * 60 * 60 * 24 * 7; LocalDateTime currentTime = LocalDateTime.now(); LocalDateTime initialTime = currentTime.withHour(18).withMinute(0).withSecond(0).withNano(0).with(DayOfWeek.THURSDAY); //如果当前时间星期数大于四,那么就直接变为下周四,而不是继续变为本周四 if (currentTime.compareTo(initialTime) > 0) { initialTime = initialTime.plusWeeks(1); } long initialDelay = Duration.between(currentTime, initialTime).toMillis(); pool.scheduleAtFixedRate(() -> System.out.println("running"), initialDelay, period, TimeUnit.MILLISECONDS); }
代码内容如注释所述。
本文作者:Appletree24
本文链接:https://www.cnblogs.com/appletree24/p/17142610.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步