多线程之ThreadPoolExecutor

转自:  http://my.oschina.net/jielucky/blog/157250
1
2
3
4
5
6
7
8
9
10
11
12
ThreadPoolExecutor pool = new ThreadPoolExecutor(corePoolSize,
                                 maximumPoolSize,
                                 keepAliveTime,
                                 unit,
                                 workQueue,
                                 handler);
//corePoolSize: 线程池维护线程的最少数量  
//maximumPoolSize:线程池维护线程的最大数量  
//keepAliveTime: 线程池维护线程所允许的空闲时间  
//unit: 线程池维护线程所允许的空闲时间的单位  
//workQueue: 线程池所使用的缓冲队列  
//handler: 线程池对拒绝任务的处理策略 

一个 ExecutorService,它使用可能的几个池线程之一执行每个提交的任务,通常使用 Executors 工厂方法配置。

  1. 线程池可以解决两个不同问题:由于减少了每个任务调用的开销,它们通常可以在执行大量异步任务时提供增强的性能,并且还可以提供绑定和管理资源(包括执行任务集时使用的线程)的方法。每个ThreadPoolExecutor还维护着一些基本的统计数据,如完成的任务数。
  2. 为了便于跨大量上下文使用,此类提供了很多可调整的参数和扩展钩子 (hook)。但是,强烈建议程序员使用较为方便的 Executors 工厂方法Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)(固定大小线程池)和Executors.newSingleThreadExecutor()(单个后台线程),它们均为大多数使用场景预定义了设置。否则,在手动配置和调整此类时,使用以下指导:
  3. 核心和最大池大小ThreadPoolExecutor将根据 corePoolSize(参见 getCorePoolSize())和 maximumPoolSize(参见 getMaximumPoolSize())设置的边界自动调整池大小。当新任务在方法 execute(java.lang.Runnable) 中提交时,如果运行的线程少于 corePoolSize,则创建新线程来处理请求,即使其他辅助线程是空闲的。如果运行的线程多于 corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建新线程。如果设置的 corePoolSize 和 maximumPoolSize 相同,则创建了固定大小的线程池。如果将 maximumPoolSize 设置为基本的无界值(如Integer.MAX_VALUE),则允许池适应任意数量的并发任务。在大多数情况下,核心和最大池大小仅基于构造来设置,不过也可以使用 setCorePoolSize(int) 和 setMaximumPoolSize(int) 进行动态更改。
  4. 按需构造默认情况下,即使核心线程最初只是在新任务到达时才创建和启动的,也可以使用方法 prestartCoreThread() 或 prestartAllCoreThreads() 对其进行动态重写。如果构造带有非空队列的池,则可能希望预先启动线程。
  5. 创建新线程使用 ThreadFactory 创建新线程。如果没有另外说明,则在同一个 ThreadGroup 中一律使用 Executors.defaultThreadFactory() 创建线程,并且这些线程具有相同的NORM_PRIORITY优先级和非守护进程状态。通过提供不同的 ThreadFactory,可以改变线程的名称、线程组、优先级、守护进程状态,等等。如果从newThread返回 null 时ThreadFactory未能创建线程,则执行程序将继续运行,但不能执行任何任务。
  6. 保持活动时间如果池中当前有多于 corePoolSize 的线程,则这些多出的线程在空闲时间超过 keepAliveTime 时将会终止(参见getKeepAliveTime(java.util.concurrent.TimeUnit))。这提供了当池处于非活动状态时减少资源消耗的方法。如果池后来变得更为活动,则可以创建新的线程。也可以使用方法 setKeepAliveTime(long, java.util.concurrent.TimeUnit) 动态地更改此参数。使用Long.MAX_VALUE TimeUnit.NANOSECONDS 的值在关闭前有效地从以前的终止状态禁用空闲线程。默认情况下,保持活动策略只在有多于 corePoolSizeThreads 的线程时应用。但是只要 keepAliveTime 值非 0, allowCoreThreadTimeOut(boolean) 方法也可将此超时策略应用于核心线程。
  7. 排队所有 BlockingQueue 都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互:
参数介绍:
        corePoolSize 核心线程数,指保留的线程池大小(不超过maximumPoolSize值时,线程池中最多有corePoolSize 个线程工作)。 
        maximumPoolSize 指的是线程池的最大大小(线程池中最大有corePoolSize 个线程可运行)。 
        keepAliveTime 指的是空闲线程结束的超时时间(当一个线程不工作时,过keepAliveTime 长时间将停止该线程)。 
        unit 是一个枚举,表示 keepAliveTime 的单位(有NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS,7个可选值)。 
        workQueue 表示存放任务的队列(存放需要被线程池执行的线程队列)。 
        handler 拒绝策略(添加任务失败后如何处理该任务).
        1、线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
        2、当调用 execute() 方法添加一个任务时,线程池会做如下判断:
            a. 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
            b. 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。
            c. 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这个任务;
            d. 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”。
        3、当一个线程完成任务时,它会从队列中取下一个任务来执行。
        4、当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。

              这个过程说明,并不是先加入任务就一定会先执行。假设队列大小为 4,corePoolSize为2,maximumPoolSize为6,那么当加入15个任务时,执行的顺序类似这样:首先执行任务 1、2,然后任务3~6被放入队列。这时候队列满了,任务7、8、9、10 会被马上执行,而任务 11~15 则会抛出异常。最终顺序是:1、2、7、8、9、10、3、4、5、6。当然这个过程是针对指定大小的ArrayBlockingQueue<Runnable>来说,如果是LinkedBlockingQueue<Runnable>,因为该队列无大小限制,所以不存在上述问题。

一:排队有三种通用策略:
  1. 直接提交工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
  2. 无界队列使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
  3. 有界队列当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
  • 拒绝的任务当 Executor 已经关闭,并且 Executor 将有限边界用于最大线程和工作队列容量,且已经饱和时,在方法 execute(java.lang.Runnable) 中提交的新任务将被 拒绝。在以上两种情况下,execute方法都将调用其 RejectedExecutionHandler 的 RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) 方法。下面提供了四种预定义的处理程序策略:
  1. 在默认的 ThreadPoolExecutor.AbortPolicy 中,处理程序遭到拒绝将抛出运行时 RejectedExecutionException。
  2. 在 ThreadPoolExecutor.CallerRunsPolicy 中,线程调用运行该任务的execute本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
  3. 在 ThreadPoolExecutor.DiscardPolicy 中,不能执行的任务将被删除。
  4. 在 ThreadPoolExecutor.DiscardOldestPolicy 中,如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)。

定义和使用其他种类的 RejectedExecutionHandler 类也是可能的,但这样做需要非常小心,尤其是当策略仅用于特定容量或排队策略时。

  • 钩子 (hook) 方法此类提供protected可重写的 beforeExecute(java.lang.Thread, java.lang.Runnable) 和 afterExecute(java.lang.Runnable, java.lang.Throwable) 方法,这两种方法分别在执行每个任务之前和之后调用。它们可用于操纵执行环境;例如,重新初始化 ThreadLocal、搜集统计信息或添加日志条目。此外,还可以重写方法 terminated() 来执行 Executor 完全终止后需要完成的所有特殊处理。如果钩子 (hook) 或回调方法抛出异常,则内部辅助线程将依次失败并突然终止。
  • 队列维护方法 getQueue() 允许出于监控和调试目的而访问工作队列。强烈反对出于其他任何目的而使用此方法。 remove(java.lang.Runnable) 和 purge() 这两种方法可用于在取消大量已排队任务时帮助进行存储回收。
  • 终止程序 AND 不再引用的池没有剩余线程会自动shutdown。如果希望确保回收取消引用的池(即使用户忘记调用 shutdown()),则必须安排未使用的线程最终终止:设置适当保持活动时间,使用 0 核心线程的下边界和/或设置 allowCoreThreadTimeOut(boolean)。   

    那么线程池的排除策略是什么样呢,一般按如下规律执行:

        A.  如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。
        B.  如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。
        C.  如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。

   处理任务的优先级为: 

         核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。  

         当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。  

         unit可选的参数为java.util.concurrent.TimeUnit中的几个静态属性:  NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS。  

         workQueue我常用的是:java.util.concurrent.LinkedBlockingQueue<E>  

  handler有四个选择: <根据自己的业务来选择> 
    1:ThreadPoolExecutor.AbortPolicy()  
        抛出java.util.concurrent.RejectedExecutionException异常  
    2:ThreadPoolExecutor.CallerRunsPolicy()  
        重试添加当前的任务,他会自动重复调用execute()方法  
    3:ThreadPoolExecutor.DiscardOldestPolicy()  
        抛弃旧的任务  
   4:ThreadPoolExecutor.DiscardPolicy()  
        抛弃当前的任务 


总结:

  1.  线程池可立即运行的最大线程数 即maximumPoolSize 参数。
  2.  线程池能包含的最大线程数 = 可立即运行的最大线程数 + 线程队列大小 (一部分立即运行,一部分装队列里等待)
  3.  核心线程数可理解为建议值,即建议使用的线程数,或者依据CPU核数
  4.  add,offer,put三种添加线程到队列的方法只在队列满的时候有区别,add为抛异常,offer返回boolean值,put直到添加成功为止。
  5. 同理remove,poll, take三种移除队列中线程的方法只在队列为空的时候有区别, remove为抛异常,poll为返回boolean值, take等待直到有线程可以被移除。
      

扩展示例。此类的大多数扩展可以重写一个或多个受保护的钩子 (hook) 方法。例如,下面是一个添加了简单的暂停/恢复功能的子类: 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class PausableThreadPoolExecutor extends ThreadPoolExecutor {
    private boolean isPaused;
    private ReentrantLock pauseLock = new ReentrantLock();
    private Condition unpaused = pauseLock.newCondition();
 
    public PausableThreadPoolExecutor(...) { super (...); }
  
    protected void beforeExecute(Thread t, Runnable r) {
      super .beforeExecute(t, r);
      pauseLock.lock();
      try {
        while (isPaused) unpaused.await();
      } catch (InterruptedException ie) {
        t.interrupt();
      } finally {
        pauseLock.unlock();
      }
    }
  
    public void pause() {
      pauseLock.lock();
      try {
        isPaused = true ;
      } finally {
        pauseLock.unlock();
      }
    }
  
    public void resume() {
      pauseLock.lock();
      try {
        isPaused = false ;
        unpaused.signalAll();
      } finally {
        pauseLock.unlock();
      }
    }
  }

下面是ThreadPoolExecutor例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package com.thread.threadpool;
 
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
 
public class ThreadPool {
     private int corePoolSize = 1 ; // 线程池维护线程的最少数量
     private int maximumPoolSize = 10 ; // 线程池维护线程的最大数量
     private long keepAliveTime = 3 ; // 线程池维护线程所允许的空闲时间
     private TimeUnit unit = TimeUnit.SECONDS; // 线程池维护线程所允许的空闲时间的单位
     private BlockingQueue<Runnable> workQueue; // 线程池所使用的缓冲队列
     private RejectedExecutionHandler handler; // 线程池对拒绝任务的处理策略
     private static AtomicLong along = new AtomicLong( 0 );
 
     public void run() throws InterruptedException {
         ThreadPoolExecutor pool = new ThreadPoolExecutor(corePoolSize,
                 maximumPoolSize, keepAliveTime, unit,
                 new LinkedBlockingQueue<Runnable>(),
                 new ThreadPoolExecutor.DiscardOldestPolicy()) {
 
             // 线程执行之前运行
             @Override
             protected void beforeExecute(Thread t, Runnable r) {
                 System.out.println( "...............beforeExecute" );
             }
 
             // 线程执行之后运行
             @Override
             protected void afterExecute(Runnable r, Throwable t) {
                 System.out.println( "...............afterExecute" );
             }
 
             // 整个线程池停止之后
             protected void terminated() {
                 System.out.println( "...............thread stop" );
             }
         };
         for ( int i = 1 ; i <= 10 ; i++) {
             pool.execute( new ThreadPoolTask(i, along));
         }
         for ( int i = 1 ; i <= 10 ; i++) {
             pool.execute( new ThreadPoolTask(-i, along));
         }
         pool.shutdown();
         Thread.sleep( 25000 );
         System.out.println(along.get());
 
     }
 
     public static void main(String[] args) {
         try {
             new ThreadPool().run();
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
     }
}
 
class ThreadPoolTask implements Runnable {
     private int i = 0 ;
     private AtomicLong along;
 
     ThreadPoolTask( int i, AtomicLong along) {
         this .i = i;
         this .along = along;
     }
 
     @Override
     public void run() {
         try {
             // 模拟业务逻辑
             Thread.sleep( 1000 );
             along.addAndGet(i);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println(Thread.currentThread().getName() + "  " + i);
     }
 
}
这篇文章也写的不错:  http://my.oschina.net/20076678/blog/33392  

线程池shutdown()方法和shutdownNow()区别:

  • shutdown()  不允许添加新的任务,等池中所有的任务执行完毕之后再关闭线程池。
  • shutdownNow() 不允许添加新的任务。立刻关闭线程池。不管池中是否还存在正在运行的任务。关闭顺序是先尝试关闭当前正在运行的任务。然后返回待完成任务的清单。已经运行的任务则不返回。

一般创建线程池大小不会固定值。一般都是根据系统cpu、io、内存等等信息来计算出来的。一般情况下只要不"太大"或者"太小"就可以了。可以用Runtime.getRuntime().availableProcessors()来获取当前jvm中cpu数目然后乘以每个cpu处理任务数即可。

  1. public static boolean interrupted
    测试当前线程是否已经中断。线程的中断状态 由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。
  2. public boolean isInterrupted()
    测试线程是否已经中断。线程的中断状态不受该方法的影响。
  3. public void interrupt()
    中断线程。

    其中,interrupt方法是唯一能将中断状态设置为true的方法。静态方法interrupted会将当前线程的中断状态清除,但这个方法的命名极不直观,很容易造成误解,需要特别注意

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ThreadPoolExecutor是Python标准库concurrent.futures中的一个类,它提供了一种简单的方式来在多个线程中执行可调用对象。在Tkinter中使用ThreadPoolExecutor可以实现多线程操作,从而提高程序的效率。以下是一个使用ThreadPoolExecutor的Tkinter多线程示例: ```python import tkinter as tk from concurrent.futures import ThreadPoolExecutor class App(tk.Tk): def __init__(self): super().__init__() self.title("Tkinter多线程示例") self.geometry("300x200") self.label = tk.Label(self, text="点击按钮开始计算") self.label.pack(pady=20) self.button = tk.Button(self, text="开始", command=self.start_calculation) self.button.pack(pady=10) self.executor = ThreadPoolExecutor(max_workers=2) def start_calculation(self): self.button.config(state="disabled") self.label.config(text="正在计算...") self.executor.submit(self.calculation_complete) def calculation_complete(self): # 模拟计算 import time time.sleep(5) self.label.config(text="计算完成") self.button.config(state="normal") if __name__ == "__main__": app = App() app.mainloop() ``` 在这个示例中,我们创建了一个Tkinter应用程序,包含一个标签和一个按钮。当用户点击按钮时,我们使用ThreadPoolExecutor在后台启动一个新线程来模拟计算,并在计算完成后更新标签的文本。注意,在Tkinter中更新UI必须在主线程中进行,因此我们使用submit方法将计算任务提交给ThreadPoolExecutor,而不是直接在新线程中更新UI。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值