在日常开发中,我们有时会遇到遇到多线程处理任务的情况,JDK里提供了便利的 ThreadPoolExecutor以及其包装的工具类Executors。但是我们知道 ExecutorService.excute(Runnable r)是异步的,超过线程池处理能力的线程会被加入到执行队列里。有时候为了保证任务提交的顺序性,我们不希望有这个执行队列,在线程池满的时候,则把主线 程阻塞。那么,怎么实现呢?

最直接的想法是继承ThreadPoolExecutor,重载excute()方法,加入线程池是否已满的检查,若线程池已满,则等待直到上一个 任务执行完毕。这里ThreadPoolExecutor提供了一个afterExecute(Runnable r, Throwable t)方法,每个任务执行结束时会调用这个方法。 同时,我们会用到concurrent包的ReentrantLock以及Condition.wait/notify方法。以下是实现代码(代码来自:http://www.cnblogs.com/steeven/archive/2005/12/08/293219.html):

<!-- lang: java --> private ReentrantLock pauseLock = new ReentrantLock(); private Condition unpaused = pauseLock.newCondition(); @Overridepublic void execute(Runnable command) {pauseLock.lock();  try {   while (getPoolSize()==getMaximumPoolSize() && getQueue().remainingCapacity()==0)unpaused.await();   super.execute(command);//放到lock外面的话,在压力测试下会有漏网的!} catch (InterruptedException e) {log.warn(this, e);} finally {pauseLock.unlock();}} @Overrideprotected void afterExecute(Runnable r, Throwable t) {  super.afterExecute(r,t);  try{pauseLock.lock();unpaused.signal();}finally{pauseLock.unlock();}}

当然,有些熟悉JDK源码的人会说,自己实现这个太费劲了,不喜欢!有没有比较简单的方法呢?

这里介绍一下vela同学的方法:http://vela.diandian.com/post/2012-07-24/40031283329

研究ThreadPoolExecutor.excute()源码会发现,它调用了BlockingQueue.offer()来实现多余任务的入 队。BlockingQueue有两个方法:BlockingQueue.offer()和BlockingQueue.put(),前者在队列满时不阻 塞,直接失败,后者在队列满时阻塞。那么,问题就很简单了,继承某个BlockingQueue,然后将offer()重写,改成调用put()就搞定 了!最短的代码量,也能起到很好的效果哦!

<!-- lang: java -->package com.diandian.framework.concurrent;import java.util.concurrent.ExecutorService;import java.util.concurrent.LinkedBlockingQueue;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;public class ExecutorsEx {    /*** 创建一个堵塞队列* * @param threadSize* @return*/public static ExecutorService newFixedThreadPool(int threadSize) {        return new ThreadPoolExecutor(threadSize, threadSize, 0L, TimeUnit.MILLISECONDS,                new LinkedBlockingQueue<Runnable>(1) {                    private static final long serialVersionUID = -9028058603126367678L;                    @Overridepublic boolean offer(Runnable e) {                        try {put(e);                            return true;} catch (InterruptedException ie) {Thread.currentThread().interrupt();}                        return false;}});}
}

当然这个方法有一点让人不快的地方,因为它与我们熟知的OO基本原则之一--里氏替换原则冲突了,即子类的方法与父类的 方法有不同的行为。毕竟都是实现了BlockingQueue接口,offer()方法的行为被改变了。虽然只是一个匿名类,但是对于某些OOP的拥趸来 说总有些不爽的地方吧!

没关系,我们还有JDK默认的解决方法:使用RejectedExecutionHandler。当 ThreadPoolExecutor.excute执行失败时,会调用的RejectedExecutionHandler,这就是 ThreadPoolExecutor的可定制的失败策略机制。JDK默认提供了4种失败策略: AbortPolicy(中止)、CallersRunPolicy(调用者运行)、DiscardPolicy(丢弃)、 DiscardOldestPolicy(丢弃最旧的)。 其中值得说的是CallersRunPolicy,它会在excute失败后,尝试使用主线程(就是调用excute方法的线程)去执行它,这样就起到了 阻塞的效果!于是一个完完全全基于JDK的方法诞生了:

<!-- lang: java -->public static ExecutorService newBlockingExecutorsUseCallerRun(int size) {return new ThreadPoolExecutor(size, size, 0L, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(),new ThreadPoolExecutor.CallerRunsPolicy());
}

当然这个方法有一个问题:这样加上主线程,总是会比参数的size线程多上一个。要么在函数开始就把size-1,要么,我们可以尝试自己实现一个RejectedExecutionHandler:

<!-- lang: java -->public static ExecutorService newBlockingExecutorsUseCallerRun(int size) {    return new ThreadPoolExecutor(size, size, 0L, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(),            new RejectedExecutionHandler() {                @Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {                    try {executor.getQueue().put(r);} catch (InterruptedException e) {                        throw new RuntimeException(e);}}});
}

怎么样,这下是不是感觉挺好了呢?


2013年9月22日更新:

事实证明,除了JDK的CallerRunsPolicy方案,其他的方案都存在一个隐患:

如果线程仍在执行,此时显式调用ExecutorService.shutdown()方法,会因为还有一个线程阻塞没有入队,而此时线程已经停止了,而这个元素才刚刚入队,最终会导致RejectedExecutionException

转载于:https://blog.51cto.com/chengyou/1834566

Java里阻塞线程的三种实现方法相关推荐

  1. Java 创建一个线程的三种方式

    Java 创建一个线程的三种方式 更多内容,点击了解: https://how2j.cn/k/thread/thread-start/353.html 创建多线程有3种方式,分别是继承线程类,实现Ru ...

  2. java map集合遍历方法,Java的Map集合的三种遍历方法

    集合的一个很重要的操作---遍历,学习了三种遍历方法,三种方法各有优缺点~~ 1. package com.myTest.MapText; import java.util.Collection; i ...

  3. JAVA中创建线程的三种方法及比较

    JAVA中创建线程的方式有三种,各有优缺点,具体如下: 目录 一.继承Thread类来创建线程 二.实现Runnable接口来创建线程 三.通过Callable和Future来创建线程 四.三种方式创 ...

  4. 多线程系列教材 (一)- Java 创建一个线程的三种方式

    多线程即在同一时间,可以做多件事情. 创建多线程有3种方式,分别是继承线程类,实现Runnable接口,匿名类 步骤1:线程概念 步骤2:创建多线程-继承线程类 步骤3:创建多线程-实现Runnabl ...

  5. java开启新线程的三种方法

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. 方式1:继承Thread类 步骤: 1):定义一个类A继承于Java.lang.Thread类. 2 ...

  6. Java中创建线程的三种方式

    Java中创建线程主要有三种方式: 一.继承Thread类创建线程类 (1)继承Thread类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务.因此把run()方法称为执行体. ...

  7. zmq java 消息阻塞_ZMQ的三种消息模式

    一. ZMQ是什么? 普通的socket是端对端(1:1)的关系,ZMQ是N:M的关系,socket的连接需要显式地建立连接,销毁连接,选择协议(TCP/UDP)和 错误处理,ZQM屏蔽了这些细节,像 ...

  8. java字符串是否相等的三种判断方法

    1. == 比较的是否是同一对象 eg:String str1="abc",str2="abc" : if(str1==str2){} 结果为true 因为在j ...

  9. Java 创建线程的三种方式

    一.继承Thread类创建 1.定义一个类继承Thread类,并重写Thread类的run()方法,run()方法的方法体就是线程要完成的任务,因此把run()称为线程的执行体: 2.创建该类的实例对 ...

最新文章

  1. R语言sys方法:sys.getpid函数获取R会话的进程ID、sys.glob函数和file.path函数匹配文件夹下的所有特定类型文件、sys.info函数获取系统和用户信息
  2. 信息系统项目管理师-组织级、流程管理核心知识点思维脑图
  3. 用python画哆啦a梦的代码解释_python画哆啦A梦和大雄
  4. 《C++标准程序库》学习笔记5 — 第七章
  5. 【JVM学习笔记一】JVM内存分布
  6. review board 使用
  7. 特别推荐BLOG(一) 程序猿DD的博客
  8. 在WIN10中安装经典计算器
  9. Blow Up 3macOS图片放大锐利的详细使用教程与安装方法
  10. 国美易卡设计构架的思维模式(国美易卡)
  11. Pearl Pairing
  12. mysql explain不准确_mysql explain预估剖析
  13. Google Gmail Oauth Client ID 认证指南
  14. ds18b20温度转换指令_DS18B20传感器温度转换指令( )。
  15. PDF怎么添加页码?PDF添加页码的方法
  16. c语言单链表的按序号查找,以下为单链表按序号查找的运算,分析算法,请在______处填上正确的语句。 pointer find_lklist(1kl...
  17. 英语语法---连接词详解
  18. GNU的C++代码书写规范,C语言之父Dennis Ritchie亲自修订 (转)
  19. dashboard 镜像源_kubernetes相关镜像源地址汇总
  20. 苹果手机图片黑白互换_苹果与3周显卡互换

热门文章

  1. 福利 | 抽奖送现金送书《Web前端工程师修炼之道》
  2. 自动化测试===adb 解锁手机的思路
  3. SQL Server 连接超时案例一则
  4. 收集网络状态(Ping),并用邮件通知管理员
  5. GCT之数学公式(三角函数)
  6. C++ Exercises(十五)--排序算法的简单实现
  7. docker 修改服务器,docker-修改容器挂载目录的3种方法小结
  8. 关于epoll,select,poll的理解
  9. Jzoj5317 Func
  10. web框架之Django(一)