JavaConcurrencyInPractice-线程池的使用

1、摘要

  线程池的使用使得任务的提交与执行解耦开来,但是,线程池可以执行所有任务吗?

  答案是——不能,线程池中封装了任务的执行策略,提供了几种执行策略的实现,

  但是,不是所有的任务都能由这些策略来执行的:

    1、依赖性任务,任务的执行依赖其他任务。

    2、使用线程封闭机制的任务,用支持多线程的线程池来执行线程封闭的任务会失去并发安全性。

    3、对响应时间敏感的任务,线程池中的线程数目太少,会限制响应速率。

    4、使用了ThreadLocal的任务,线程池中的线程会被新线程替换掉(出故障消亡时),这是ThreadLocal中保存的信息会被丢失。

在一些任务中,需要拥有或排除某些特定的执行策略。
如果某些任务依赖于线程池中执行的其他任务,就要求线程池足够的大,从而确保不会发生死锁。
线程封闭的任务需要串行执行。
将这些需求写入文档,防止代码的维护人员因为使用了某种不合适的执行策略而导致问题。

 

 

2、线程池的大小

  饥饿死锁:任务间有依赖关系,依赖任务不能得到执行,导致死锁。

    解决办法:提升线程池中线程的数量。(治标不治本)

  线程池大小过大,造成资源消耗;过小,资源利用率不高,且可能会发生活跃性问题。

  设置线程池的大小需要考虑:

    CPU数量,内存大小,任务类型(IO或计算密集型),任务是否需要稀缺资源,

    对于计算密集型任务,在有N个CPU的机器上,N+1大小的线程池通常最为适宜。

    对于包含IO密集型的任务,线程池的大小应该更大。

     

2、配置线程池

  Executors中提供了许多的现有的线程池的实现,我们也可以使用ThreadPoolExecutor来定制自己的线程池。

  定制自己的线程池之前,先了解一下线程池的工作机制:

    线程池中包含了一定数量的线程。这些线程不断地执行从任务队列中取出的任务。

    在请求到达速率超过了线程池的处理速率时,任务会在一个Runnable队列中等待。

    ThreadPoolExecutor提供了一个BlockingQueue来保存任务,可以有三种排队方法:

      1、有界队列  2、无界队列  3、同步移交

  在任务队列满了时:

    饱和策略将用来决定如何处理新提交的任务。

    可以通过setRejectedExecutionHandler来修改饱和策略。

    标准的Executor都有几种不同的饱和策略:

      AbortPolicy:继续提交任务时,将抛出RejectedExecutionException异常,调用者可以捕获该异常。

      CallerRunsPolicy:任务会被分配到调用者线程中执行。调用者线程在执行期间不能继续提交任务。

      DiscardPolicy:丢弃提交的任务。

      DiscardOldestPolicy:丢弃最先提交的任务。

  限制任务提交速率:

    即,限制任务提交数量,在任务队列满时,提交操作阻塞。——使用Semaphore

/**
* 使用Semaphore来限制任务的提交速率
* 在任务队列满时,提交操作阻塞
*/
@ThreadSafe 
public class BoundedExecutor { 
     private final Executor exec; 
     private final Semaphore semaphore; 
     //bound为最多能够提交的数量
     public BoundedExecutor(Executor exec, int bound) { 
         this.exec = exec; 
         this.semaphore = new Semaphore(bound); 
     } 

     public void submitTask(final Runnable command)  throws InterruptedException { 
         semaphore.acquire(); 
         try { 
             exec.execute(new Runnable() { 
                 public void run() { 
                     try { 
                         command.run(); 
                     } finally { 
                         semaphore.release(); 
                     } 
                 } 
             }); 
         } catch (RejectedExecutionException e) { 
             semaphore.release(); 
         } 
     } 
}     

 

 

3、在线程池中创建线程

  每当线程池需要创建线程时,都是通过线程工厂方法来完成的。线程工厂方法返回一个线程对象。

  可以实现ThreadFactory接口来定义自己的线程工厂,并将其用于线程池。

/**
* 继承Thread,扩展实现线程生命周期的日志记录及绑定未检查异常处理器
*/
public class MyAppThread extends Thread { 
     public static final String DEFAULT_NAME = "MyAppThread"; 
     private static volatile boolean debugLifecycle = false; 
     private static final AtomicInteger created = new AtomicInteger(); 
     private static final AtomicInteger alive = new AtomicInteger(); 
     private static final Logger log = Logger.getAnonymousLogger(); 
     public MyAppThread(Runnable r) { this(r, DEFAULT_NAME); } 
     public MyAppThread(Runnable runnable, String name) { 
         super(runnable, name + "-" + created.incrementAndGet()); 
         setUncaughtExceptionHandler( new Thread.UncaughtExceptionHandler() { 
             public void uncaughtException(Thread t,  Throwable e) { 
                 log.log(Level.SEVERE,  "UNCAUGHT in thread " + t.getName(), e); 
             } 
         }); 
     } 

     public void run() { 
         // Copy debug flag to ensure consistent value throughout. 
         boolean debug = debugLifecycle; 
         if (debug) log.log(Level.FINE, "Created "+getName()); 
         try { 
             alive.incrementAndGet(); 
             super.run(); 
         } finally { 
             alive.decrementAndGet(); 
             if (debug) log.log(Level.FINE, "Exiting "+getName()); 
         } 
     } 
     public static int getThreadsCreated() { return created.get(); } 
     public static int getThreadsAlive() { return alive.get(); } 
     public static boolean getDebug() { return debugLifecycle; } 
     public static void setDebug(boolean b) { debugLifecycle = b; } 
} 

/**
* 实现ThreadFactory 接口,返回自定义的线程实现
*/
public class MyThreadFactory implements ThreadFactory { 
     private final String poolName; 
     public MyThreadFactory(String poolName) { 
         this.poolName = poolName; 
     } 
     public Thread newThread(Runnable runnable) { 
         return new MyAppThread(runnable, poolName); 
     } 
} 

 

 

4、扩展ThreadPoolExecutor

  可以重写beforeExecute、afterExecute和terminated方法来扩展ThreadPoolExecutor的功能。 

/**
* 覆写beforeExecute、afterExecute、terminated方法来扩展ThreadPoolExecutor
* 添加了任务执行前,执行后以及线程池关闭时的日志记录
*/
public class TimingThreadPool extends ThreadPoolExecutor { 
     private final ThreadLocal<Long> startTime = new ThreadLocal<Long>(); 
     private final Logger log = Logger.getLogger("TimingThreadPool"); 
     private final AtomicLong numTasks = new AtomicLong(); 
     private final AtomicLong totalTime = new AtomicLong(); 

     protected void beforeExecute(Thread t, Runnable r) { 
         super.beforeExecute(t, r); 
         log.fine(String.format("Thread %s: start %s", t, r)); 
         startTime.set(System.nanoTime()); 
     } 

     protected void afterExecute(Runnable r, Throwable t) { 
         try { 
             long endTime = System.nanoTime(); 
             long taskTime = endTime - startTime.get(); 
             numTasks.incrementAndGet(); 
             totalTime.addAndGet(taskTime); 
             log.fine(String.format("Thread %s: end %s, time=%dns", t, r, taskTime)); 
         } finally { 
             super.afterExecute(r, t); 
         } 
     } 

     protected void terminated() { 
         try { 
             log.info(String.format("Terminated: avg time=%dns", totalTime.get() / numTasks.get())); 
         } finally { 
             super.terminated(); 
         } 
     } 
} 

 

 

5、递归算法并行化

  在循环中进行的计算或IO操作,只要每次迭代时相互独立的,就可以将串行计算转换为并行计算,

  而不用等待计算完毕后再进行下一轮迭代。

void processSequentially(List<Element> elements) { 
     for (Element e : elements) 
         process(e);     
} 
// 每次迭代独立,则可以转换为并行
void processInParallel(Executor exec, List<Element> elements) { 
     for (final Element e : elements) 
         exec.execute(new Runnable() { 
             public void run() { process(e); } 
         }); 
}

  这非常适合于递归算法,尤其是在进入下一层时需要对当前层进行计算的递归算法。

/**
* 将枚举并记录节点信息的递归算法改为并行
*/
public<T> void sequentialRecursive(List<Node<T>> nodes, Collection<T> results) { 
     for (Node<T> n : nodes) { 
         results.add(n.compute());     //计算不需要串行,可以并行处理
         sequentialRecursive(n.getChildren(), results);    //遍历过程有顺序要求,需要串行 
     } 
} 
public<T> void parallelRecursive(final Executor exec,  List<Node<T>> nodes,  final Collection<T> results) { 
     for (final Node<T> n : nodes) { 
         exec.execute(new Runnable() { 
             public void run() { 
                 results.add(n.compute()); 
             } 
         }); 
         parallelRecursive(exec, n.getChildren(), results); 
     } 
} 

 

  

 

posted @ 2021-09-18 15:13  Lqblalala  阅读(70)  评论(0编辑  收藏  举报