07. 线程池原理(ThreadPool)

一 安卓中线程的表现形式

主线程(也叫UI线程)在java中默认情况下一个进程只有一个线程,这个线程就是主线程,其主要作用是处理和界面交互相关的逻辑,

主线程不能做太多耗时的操作,否则会产生界面卡顿的感觉。为保持较快的响应速度,子线程就出场了。

子线程:也叫工作线程,除了主线程之外的都是子线程;

基本用途:主线程是运行四大组件及处理它们和用户的交互,子线程处理耗时的任务,如网络请求、I/O操作等。

简单介绍一下后三者的注意事项 Thread、AsyncTask、HanderThread、IntentService.

1.AysncTask 异步线程

为轻量级的线程池,异步任务类,其封装了Thread和Handler,通过AysncTask可以很方便的执行后台任务及在主线程中访问UI。

缺点:不适合进行特别耗时的后台任务,对于特别耗时的任务最好用池线程池替换。

提供两个线程池:

  1)THREAD_POOL_EXECUTOR异步线程池

  2)SERIAL_EXECUTOR同步线程池

 使用方式:

  new AsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, 0);

  注意:

  1) 当默认使用AsyncTask.execute()执行异步任务,默认使用SERIAL_EXECUTOR.

  2) SERIAL_EXECUTOR是在THREAD_POOL_EXECUTOR基础上,加一个task的集合来维护顺序的。

2.HandlerThread

继承自Thread,它是一种可以 使用Handler和Thread.实现也很简单,

在run方法中通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环。

3.IntentService

继承自Service,是一个抽象类,所以必须创建他的子类才能使用IntentService,可用于执行后台耗时任务,

当任务完成后会自动停止,该优先级高于其他单纯的线程,不易补系统杀死。IntentService封装了Handler和HandlerThread.

二 为啥使用线程池

1.使用线程池的原因

1) 创建异步线程的弊端

每次new Thread创建线程和销毁线程 是要消耗资源的,导致性能变差;

缺乏统一的管理,可能导致无限制的线程运行,严重的后果就是OOM或死机。

2) T1创建 T2任务执行 T3线程销毁 T1+T2+T3,用线程池 将T1和T2时间节省;

3) 线程稀缺昂贵: 缺乏统一的管理,可能导致无限制的线程运行,严重的后果就是OOM 或者死机。

2.使用线程池的优点

1>降低资源消耗: 重用性大,减少对象的创建,提高性能。

2>提高响应速度: 可有效控制并发线程数,提高系统资源利用率,避免资源争夺。

3>提高线程的可管理性:对多个线程进行统一地管理,避免资源竞争中出现的问题,可提供定时、循环、并发、单一等功能。

3.Android 线程池的好处

1>Android中像访问内存卡,联网等耗时较长的任务时,需要在异步线程中进行,所以需要新开线程进行。

但创建或者销毁线程伴随着系统的开销,频繁的创建和销毁线程,会较大的影响系统的性能。使用线程池,

可用已有的闲置线程来执行新任务。

2>我们知道线程能共享系统资源,如果同时执行的线程过多,就有可能导致系统资源不足而产生阻塞的情况,

运用线程池能有效的控制线程最大并发数(maximumPoolSize),避免以上的问题。

3>线程池能够对线程进行有效的管理,比如延时执行,定时定周期循环执行等。

三 线程池的工作原理

(当一个任务提交到线程池时↓)

1)首先判断核心线程池中的线程是否已经满了,如果没满,则创建一个核心线程执行任务;(注意执行这一步,要获取全局锁)

2)如果核心池中线程已满,则判断任务阻塞队列是否已满,没有满则加入阻塞队列;

3)如果任务阻塞队列已满,判断线程池中的线程数是否达到了最大值,没达到则创建非核心线程执行任务,

4)否则执行饱和拒绝策略,拒绝策略有4中,默认抛出异常;

四 线程池的用法

0. ThreadPoolExecutor类

Executor是一个接口,它是Executor框架的基础,它将任务的提交与任务的执行分离开来。

ExecutorService接口继承了Executor,在其上做了一些shutdown()、submit()的扩展,可以说是真正的线程池接口;

AbstractExecutorService抽象类实现了ExecutorService接口中的大部分方法;

ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务。

ScheduledExecutorService接口继承了ExecutorService接口,提供了带"周期执行"功能ExecutorService;

ScheduledThreadPoolExecutor是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令。

ScheduledThreadPoolExecutor比Timer更灵活,功能更强大。

1.线程池的创建各个参数含义

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime, TimeUnit unit,
                          BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

corePoolSize

线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;

如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;

如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。

maximumPoolSize

线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;

keepAliveTime

线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时间。默认情况下,该参数只在线程数大于corePoolSize时才有用;

TimeUnit

keepAliveTime的时间单位

workQueue

workQueue必须是BlockingQueue阻塞队列。当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待。通过workQueue,线程池实现了阻塞功能。一般来说,我们应该尽量使用有界队列,因为使用无界队列作为工作队列会对线程池带来如下影响。

1)当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池中的线程数不会超过corePoolSize。

2)由于1,使用无界队列时maximumPoolSize将是一个无效参数。

3)由于1和2,使用无界队列时keepAliveTime将是一个无效参数。

4)使用无界queue可能会耗尽系统资源,有界队列则有助于防止资源耗尽,同时即使使用有界队列,也要尽量控制队列的大小在一个合适的范围。

threadFactory

创建线程的工厂,通过自定义的线程工厂可给每个新建的线程设置一个具有识别度的线程名,更加自由的对线程做更多的设置,如设置守护线程。

Executors静态工厂里默认的threadFactory,线程的命名规则是“pool-数字-thread-数字”。

RejectedExecutionHandler

线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:

(1)AbortPolicy:直接抛出异常,默认策略;

(2)CallerRunsPolicy:用调用者所在的线程来执行任务;

(3)DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;

(4)DiscardPolicy:直接丢弃任务;

当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。

 

2. Executors

Android中的线程池来源于java,主要通过 Executor 来派生特定类型的线程池,不同类型的线程池有不同的意义。

Executor为接口,真正实现为ThreadPoolExecutor,它提供了一系列的参数来配置线程池。不同参数意味着不同的线程池;

Java通过Executors提供了四类常用的线程池。

1> FixedThreadPool

重用固定数量线程的线程池特点:

*1.可控制线程最大并发数(线程数固定)

*2.超出的线程会在队列中等待

适用场景:能够更快的响应外界的请求。

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
              0L,TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<Runnable>());
}

2> CachedThreadPool

缓存线程池的特点:

*1.线程数无限制

*2.没有核心线程,都是非核心线程

适用场景:比较适合用来执行大量的但是耗时较少的任务。

public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                        60L, TimeUnit.SECONDS, 
                new SynchronousQueue<Runnable>());
}

3> ScheduledThreadPool

可以调度命令的单线程执行程序在给定的延迟后运行,或者周期性地执行

适用场景:用于执行定时任务和具有固定周期的重复任务。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

4> SingleThreadScheduledExecutor

创建可以调度命令的单线程执行程序在给定的延迟后运行,或者周期性地执行。

(注意,如果这个单线程在执行之前因为失败而终止关闭,一个新的将取代它,如果需要执行后续任务。)

任务保证执行顺序,并且在任何时候活动的任务都不超过一个。不像其他等价的东西

适用场景:按顺序执行,不需要处理同步的问题。

public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
    return new DelegatedScheduledExecutorService
        (new ScheduledThreadPoolExecutor(1, threadFactory));
}

据此 四种线程池简单介绍完成,下面介绍下如何封装使用线程池。

/**
 * 线程池封装
 */
public class ThreadPoolManager {
    private static ThreadPoolManager mInstance;


    public static ThreadPoolManager getInstance() {
        if (mInstance == null) {
            synchronized (ThreadPoolManager.class) {
                if (mInstance == null) {
                    mInstance = new ThreadPoolManager();
                }
            }
        }
        return mInstance;
    }


/**
 * 核心线程池的数量,同时能够执行的线程数量
 */
private int corePoolSize;
/**
 * 最大线程池数量,表示当缓冲队列满的时候能继续容纳的等待任务的数量
 */
private int maximumPoolSize;
/**
 * 存活时间
 */
private long keepAliveTime = 1;
private TimeUnit unit = TimeUnit.HOURS;
private ThreadPoolExecutor executor;


private ThreadPoolManager() {
    /**
     * 给corePoolSize赋值:当前设备可用处理器核心数*2 + 1,能够让cpu的效率得到最大程度执行(有研究论证的)
     */
    corePoolSize = Runtime.getRuntime().availableProcessors() * 2 + 1;
    //虽然maximumPoolSize用不到,但是需要赋值,否则报错
    maximumPoolSize = corePoolSize;
    executor = new ThreadPoolExecutor(
            //当某个核心任务执行完毕,会依次从缓冲队列中取出等待任务
            corePoolSize,
            //5,先corePoolSize,然后new LinkedBlockingQueue<Runnable>(),然后maximumPoolSize,但是它的数量是包含了corePoolSize的
            maximumPoolSize,
            //表示的是maximumPoolSize当中等待任务的存活时间
            keepAliveTime,
            unit,
            //缓冲队列,用于存放等待任务,Linked的先进先出
            new LinkedBlockingQueue<Runnable>(),
            //创建线程的工厂
        //  Executors.defaultThreadFactory(),
            new DefaultThreadFactory(Thread.NORM_PRIORITY, "tiaoba-pool-"),
            //用来对超出maximumPoolSize的任务的处理策略
            new ThreadPoolExecutor.AbortPolicy()
    );
}


/**
 * 执行任务
 * 先corePoolSize,然后new LinkedBlockingQueue<Runnable>(),再maximumPoolSize,但它包含corePoolSize的数量
 * @param runnable
 */
public void execute(Runnable runnable) {
    if (executor == null) { //线程池的创建
        //线程池执行者。
        //参1:核心线程数;参2:最大线程数;参3:线程休眠时间;参4:时间单位;
        //参5:线程队列;参6:生产线程的工厂;参7:线程拒绝处理策略
        executor = new ThreadPoolExecutor(
                //给corePoolSize赋值,当前设备可用处理器核心数*2 + 1,能够让cpu的效率得到最大程度执行
                corePoolSize,//核心线程池的数量,在核心池中同时能够执行任务的线程数量(有研究论证的)
                maximumPoolSize,//最大线程池数量,表示当缓冲队列满的时候能继续容纳的等待任务的数量
                keepAliveTime, //等待任务的存活时间,默认情况下,该参数只在线程数大于corePoolSize时才有用;
                TimeUnit.SECONDS,// keepAliveTime时间的时间单位,超过KeepAlive 杀死
                new LinkedBlockingQueue<Runnable>(),//缓冲阻塞队列,用于存放等待任务,要用有界队列
                Executors.defaultThreadFactory(),//对线程的识别度进行微调 线程命名规范"pool-数字-thread-数字"
                new ThreadPoolExecutor.AbortPolicy()//拒绝策略 RejectedExecutionHandler
               );                                   //用来对超出maximumPoolSize的任务的处理策略
    }
    if (runnable != null) executor.execute(runnable);
}




/**
 * 移除任务
 */
public void remove(Runnable runnable) {
    if (runnable != null) {
        executor.remove(runnable);
    }
}


/**
 * 创建线程的工厂,设置线程的优先级,group,以及命名
 */
private static class DefaultThreadFactory implements ThreadFactory {
    /**
     * 线程池的计数
     */
    private static final AtomicInteger poolNumber = new AtomicInteger(1);


    /**
     * 线程的计数
     */
    private final AtomicInteger threadNumber = new AtomicInteger(1);


    private final ThreadGroup group;
    private final String namePrefix;
    private final int threadPriority;


    DefaultThreadFactory(int threadPriority, String threadNamePrefix) {
        this.threadPriority = threadPriority;
        this.group = Thread.currentThread().getThreadGroup();
        namePrefix = threadNamePrefix + poolNumber.getAndIncrement() + "-thread-";
    }


    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
        if (t.isDaemon()) {
            t.setDaemon(false);
        }
        t.setPriority(threadPriority);
        return t;
    }
   }
  }


//调用方法:
ThreadPoolManager.getInstance().execute(command));
Runnable command =  new Runnable(){
    @Override
    public void run(){
    SystemClock.sleep(2000)
    }
}


3.maximumPoolSize设置

分析任务特性(run()的方法体)

CPU密集型: 频繁计算 maximumPoolSize 不要超过cpu核心数+1: Runtime.getRuntime().availableProcessors();

IO密集型: 网络通信 读取磁盘 可用CPU核心数*2 + 1,能够让cpu的效率得到最大程度执行 +1 缺页异常,防止CPU空闲;

混合型: CPU和IO兼有 相差不大的情况下 差分成两个线程池, 相差大:谁占的大,用谁的策略

CPU不直接操作IO, 而是通过给直接存储器存取(direct memory access ,DMA)发信号;IO完了,发中断信号;

用户空间--核心空间 mmap directMemory 零拷贝

4. BlockingQueue(阻塞队列)

队列是一种操作受限制的线性表,它只允许在表的前端(fornt)进行删除操作,在表的后端(rear)进行进行插入操作,

进行插入操作的端称为队尾,进行删除操作的端称为队头。在队列中插入一个元素称为入队,从队列中删除一个元素称为出队。

因队列只允许在一端插入,在另一端删除,只有最早入队的元素才能最先从队列中删除,故队列又称为先进先出(FIFO)线性表。

1>什么是阻塞队列

1)支持阻塞的插入方法:意思是当队列满时,队列会阻塞插入元素的线程,直到队列不满。

2)支持阻塞的移除方法:意思是在队列为空时,获取元素的线程会等待队列变为非空。

2>为啥用阻塞队列

  1. 使用生产者和消费者模式能解决绝大多数并发问题,该模式通过平衡生产和消费线程的工作能力来提高程序的数据处理速度。

  2. 生产者是生产数据的线程,消费者是消费数据的线程,在多线程开发中,若生产者处理很快,而消费者处理很慢,则生产者就必须等待消费者处理完,才能继续生产数据,反之亦然,为了解决这种生产消费能力不均衡的问题,便有了生产者和消费者模式。

  3. 该模式用一个容器来解决生产者和消费者的强耦合问题,生产者和消费者彼此间不直接通信,而是通过阻塞队列来进行通信,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

  4. 阻塞队列常用于生产者和消费者的场景,生产者是向队列添加元素的线程,消费者是从队列里取元素的线程,阻塞队列就是生产者用来存放元素、消费者用来获取元素的容器。

3> 生产者和消费者模式

方法/处理方式

抛出异常

返回特殊值

一直阻塞

超时退出

插入方法

add(e)

offer(e)

put(e)

offer(e,time,unit)

移除方法

remove()

poll()

take()

poll(time,unit)

检查方法

element()

peek()

不可用

不可用

  1. 抛出异常:当队列满时, 若再往队列里插入元素, 会抛出IllegalStateException("Queuefull")异常。

当队列空时, 若从队列里获取元素, 会抛出NoSuchElementException异常。

  1. 返回特殊值: 往队列插入元素时,会返回元素是否插入成功,成功返true。若是移除方法,则从队列里取出一个元素,没有返null。

  2. 一直阻塞: 当阻塞队列满时,如果生产者线程往队列里put元素,队列会一直阻塞生产者线程,直到队列可用或者响应中断退出。 当队列空时,如果消费者线程从队列里take元素,队列会阻塞住消费者线程,直到队列不为空。

  3. 超时退出: 当阻塞队列已满,若生产者线程往队列里插入元素,队列会阻塞生产者线程一段时间,若超过指定时间,线程就会退出;

  /**
   * 生产者和消费者模式
   * 多线程执行中没有加锁 会出现安全问题
   *
   * 等待区域: wait()
   * syn(持有对象锁){ 因为被锁住了,没法获取this锁
   *   while(条件不满足){
   *      对象.wait()   //内部就会释放这把锁, 这样 通知区域才可以拿到锁
   *   }
   *   //业务逻辑
   * }
   * 
   * 通知区域 notify()/notifyAll()
   * 获取对象的锁
   * syn(持有的锁, 对象锁){ //按道理 根本获取不到 this 锁, 只有等调用wait释放锁,才能获取这把锁
   *     // 业务逻辑, 改变条件
   *     do sth ;//只有走完方法才会释放锁;
   *     对象.notify()/notifyAll(); //最后调用
   * }
   *
   * notify  不确定唤醒某一个 等待的线程 没有顺序
   * notifyAll 可以唤醒全部等待中的线程;
   */
  public class ThreadCommunicationDemo {
      public static void main(String[] args) {
          Res res = new Res();
          //创建生产者任务
          ProductRunnable productRunnable = new ProductRunnable(res);
          //创建消费者任务
          ConsumeRunnable consumeRunnable = new ConsumeRunnable(res);
          //启动生产者任务
          new Thread(productRunnable).start();
          //启动消费者任务
          new Thread(consumeRunnable).start();
      }
  }
  //容器资源
  class Res {
      private int id;//面包的Id
      private boolean isConsumeable;//定义标记是否可消费,默认false:不可消费 先运行生产者 后 运行消费者


      public synchronized void put() {
          ///生产之前判断可消费标记
          if (!isConsumeable) {
              //开始生产
              id += 1;
              System.out.println(Thread.currentThread().getName() + "生产者 生产了 面包_" + id);
              //生产已经完成
              isConsumeable = true;
              //唤醒 wait();被冻结的线程,如果没有冻结的线程,没有关系,Java默认不报错,已处理
              notify();//注意:wait 或者notify 必须要有锁包裹着;
              //当前自己线程冻结,释放CPU执行权, 这个时候CPU就会去调用其他线程
              try {
                  wait();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }


      public synchronized void out() {
          if (isConsumeable) { //可消费
              //开始消费
              System.out.println(Thread.currentThread().getName() + "消费者 消费了: 面包_" + id);
              //消费完成
              isConsumeable = false;
              //唤醒wait();被冻结的线程
              notify();
              //冻结当前线程,是否CPU执行权
              try {
                  wait();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }
  }




  class ProductRunnable implements Runnable {
      private Res res;


      public ProductRunnable(Res res) {
          this.res = res;
      }


      @Override
      public void run() {
          for (int i = 0; i < 10; i++) {
              res.put();// 生产一个
          }
      }
  }


  class ConsumeRunnable implements Runnable {
      private Res res;


      public ConsumeRunnable(Res res) {
          this.res = res;
      }


      @Override
      public void run() {
          for (int i = 0; i < 10; i++) {
              res.out();//消费一个
          }
      }
  }

4> 有界无界

有限队列就是长度有限,满了以后生产者就会阻塞,

无界队列能放无数元素,不会因队列长度限制被阻塞,当然空间限制来源于系统资源的限制,若处理不及时,导致OOM 被系统杀死;

无界也会阻塞,因为阻塞不仅仅体现在生产者放入元素时会阻塞,消费者拿元素时,如果没有元素,同样也会阻塞;

5> BlockingQueue 接口

1) ArrayBlockingQueue

是一个用数组实现的有界阻塞队列,此队列按照 先进先出(FIFO) 的原则对元素进行排序。默认情况下不保证线程公平的访问队列,线程公平访问 是指先阻塞线程先访问队列, 非公平性是对先等待的线程是非公平的,当队列可用时,阻塞的线程都可以争夺访问队列的资格, 有可能先阻塞的线程最后才访问队列。初始化时传入(int capacity) 来设置队列的最大容量;

2) LinkedBlockingQueue

是一个链表结构组成的有界阻塞队列,此队列的默认和最大长度为Integer.MAX_VALUE,生产用的是putLock, 消费是 takeLock;

Array和Linked实现的区别

1. 队列中锁的实现不同

ArrayBlockingQueue实现的队列中的锁是没有分离的,即生产和消费用的是同一个锁;

LinkedBlockingQueue实现的队列中的锁是分离的,即生产用的是putLock,消费是takeLock

2. 在生产或消费时操作不同

ArrayBlockingQueue实现的队列中在生产和消费的时候,是直接将枚举对象插入或移除的;

LinkedBlockingQueue实现的队列中在生产和消费的时候,需要把枚举对象转换为Node<E>进行插入或移除,会影响性能;

3. 队列大小初始化方式不同

ArrayBlockingQueue实现的队列中必须指定队列的大小;

LinkedBlockingQueue实现的队列中可以不指定队列的大小,但是默认是Integer.MAX_VALUE

3) PriorityBlockingQueue

是一个支持优先级的无界阻塞队列。默认元素采取自然顺序升序排列。也可自定义类实现compareTo()方法来指定元素排序规则,或初始化PriorityBlockingQueue时,指定构造参数Comparator来对元素进行排序。需要注意的是不能保证同优先级元素的顺序。

4) DelayQueue

支持延时获取元素的无界阻塞队列。队列使用PriorityQueue来实现,支持延迟获取 实现Delay接口中的getDelay()方法,可用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,时间到了才能拿到;

5) SynchronousQueue

是一个不存储元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素。SynchronousQueue可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费者线程。队列本身并不存储任何元素,非常适合传递性场景。

6) LinkedTransferQueue

一个链表结构组成的无界阻塞队列,多了tryTransfer和transfer()直接给消费者,

  • transfer()

如果当前有消费者正在等待接收元素(消费者使用take()或带时间限制的poll())时,transfer方法可以把生产者传入的元素立刻传输给消费者。若没有消费者等待接收元素,transfer()会将元素存放在队列的tail节点,并等到该元素被消费者消费再返回。

  • tryTransfer()

tryTransfer()用来试探生产者传入的元素是否能直接传给消费者。如果没有消费者等待接收元素,则返回false。和transfer方法的区别是tryTransfer方法无论消费者是否接收,方法立即返回,而transfer方法是必须等到消费者消费了才返回。

7) LinkedBlockingDeque

LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列。所谓双向队列指的是可以从队列的两端插入和移出元素。双向队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。出口和入口 都能存取

5. ExecutorService 线程池 

1> newCachedThreadPoll

 定义一个线程池,当线程池里面有可复用的线程,重用线程执行,如果没有,新建线程执行。线程池容量可以无线大。

 ExecutorService cacheThread = Executors.newCachedThreadPool();
  for (int i = 0; i < 100; i++) {
      cacheThread.execute(new Runnable() {
          @Override
          public void run() {
              // TODO Auto-generated method stub                    
          }
      });
  }

2> newSingleThreadExecutor

  创建一个线程池,只有唯一的一个活动的工作线程,有序(FIFO,LIFO,优先级等顺序)的处理队列中的任务。

ExecutorService singleExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 100; i++) {
    singleExecutor.execute(new Runnable() {
        @Override
        public void run() {
            // TODO Auto-generated method stub
            
        }
    });
}

3> newFixedThreadPool

定义一个可用并发数的线程池,超过并发数的线程,在队列里面等待。

ExecutorService fixedThread = Executors.newFixedThreadPool(3);
for(int i = 0; i < 100 ; i ++){
    fixedThread.execute(new Runnable() {
        @Override
        public void run() {
            // TODO Auto-generated method sth
        }
    });
}

4> newScheduledThreadPool

//创建一个定长线程池,支持定时及周期性任务执行。
ScheduledExecutorService scheduledThread = Executors.newScheduledThreadPool(3);
//延时执行
scheduledThread.schedule(new Runnable() {
    @Override
    public void run() {
        // TODO Auto-generated method stub


    }
}, 2*1000, TimeUnit.SECONDS);
//固定的间隔执行,不受任何影响
scheduledThread.scheduleAtFixedRate(new Runnable() {


    @Override
    public void run() {
        // TODO Auto-generated method stub


    }
}, 10*1000, 1*1000, TimeUnit.SECONDS);
//当线程执行完毕后的,开始计算时间间隔再次执行
scheduledThread.scheduleWithFixedDelay(new Runnable() {
    @Override
    public void run() {
        // TODO Auto-generated method stub


    }
 }, 10*1000, 1*1000, TimeUnit.SECONDS);

6. 四种拒绝策略

RejectedExecutionHandler(interface)

建新线程造成当前运行的线程超出 maximumPoolSize,任务将被拒绝并调用RejectExecutionHandler.rejectExecution();

AbortPolicy 抛出异常(默认策略)

CallerRunsPolicy 用调用者所在线程来执行任务(谁调用谁执行)

DiscardPolicy 直接丢弃最新任务

DiscardOldestPolicy 直接抛弃阻塞队列中最靠前的任务,并执行当前的任务,

7. 执行任务

1)execute(Runnable command)方法用于提交不需要返回值的任务,故无法判断线程是否被线程池执行成功;

2)Future<?>submit(Runnable task)用于提交需返回值的任务,线程池会返一个future类型的对象,通过future.get()

方法来获取返回值,get()方法会阻塞当前线程直至任务完成,而使用get(long timeout,TimeUnit unit)方法

则会阻塞当前线程一段时间后立即返回,这时有可能任务没有执行完;

8. 销毁

原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程, 所以无法响应中断的任务可能永远无法终止。

需要自己在run方法中处理终断信号:判断isIntercepted;(线程的中断--协作机制)

shutdown(); //中断所有没有执行任务的线程 (将线程池的状态设置成SHUTDOWN状态)

shutdownNow();//全部终断(停止所有的正在执行或暂停任务的线程并将线程池的状态设置成STOP),并返回等待执行任务的列表

只要调用了这两个关闭方法中的任意一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,

这时调用isTerminaed方法会返回true。至于应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,

通常调用shutdown方法来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值