精华内容
下载资源
问答
  • java线程池原理
    2021-12-22 21:43:01

    1.线程池的优点

    1、线程是稀缺资源,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以重复使用。

    2、可以根据系统的承受能力,调整线程池中工作线程的数量,防止因为消耗过多内存导致服务器崩溃。

    2.线程池的创建

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

    corePoolSize:线程池核心线程数量

    maximumPoolSize:线程池最大线程数量

    keepAliverTime:当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间

    unit:存活时间的单位

    workQueue:存放任务的队列

    handler:超出线程范围和队列容量的任务的处理程序

    3.线程池的实现原理

    提交一个任务到线程池中,线程池的处理流程如下:

    1、判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。

    2、线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。

    3、判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
    在这里插入图片描述

    4.线程池的源码解读

    1、ThreadPoolExecutor的execute()方法

    public void execute(Runnable command) {
            if (command == null)
                throw new NullPointerException();       //如果线程数大于等于基本线程数或者线程创建失败,将任务加入队列
            if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {          //线程池处于运行状态并且加入队列成功
                if (runState == RUNNING && workQueue.offer(command)) {
                    if (runState != RUNNING || poolSize == 0)
                        ensureQueuedTaskHandled(command);
                }         //线程池不处于运行状态或者加入队列失败,则创建线程(创建的是非核心线程)
                else if (!addIfUnderMaximumPoolSize(command))           //创建线程失败,则采取阻塞处理的方式
                    reject(command); // is shutdown or saturated
            }
        }
    

    2、创建线程的方法:addIfUnderCorePoolSize(command)

    private boolean addIfUnderCorePoolSize(Runnable firstTask) {
            Thread t = null;
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                if (poolSize < corePoolSize && runState == RUNNING)
                    t = addThread(firstTask);
            } finally {
                mainLock.unlock();
            }
            if (t == null)
                return false;
            t.start();
            return true;
        }
    

    我们重点来看第7行:

    private Thread addThread(Runnable firstTask) {
            Worker w = new Worker(firstTask);
            Thread t = threadFactory.newThread(w);
            if (t != null) {
                w.thread = t;
                workers.add(w);
                int nt = ++poolSize;
                if (nt > largestPoolSize)
                    largestPoolSize = nt;
            }
            return t;
        }
    

    这里将线程封装成工作线程worker,并放入工作线程组里,worker类的方法run方法:

    public void run() {
                try {
                    Runnable task = firstTask;
                    firstTask = null;
                    while (task != null || (task = getTask()) != null) {
                        runTask(task);
                        task = null;
                    }
                } finally {
                    workerDone(this);
                }
            }
    

    worker在执行完任务后,还会通过getTask方法循环获取工作队里里的任务来执行。

    我们通过一个程序来观察线程池的工作原理:

    1、创建一个线程

    public class ThreadPoolTest implements Runnable
    {
        @Override
        public void run()
        {
            try
            {
                Thread.sleep(300);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }
    

    2、线程池循环运行16个线程:

    public static void main(String[] args)
        {
            LinkedBlockingQueue<Runnable> queue =
                new LinkedBlockingQueue<Runnable>(5);
            ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS, queue);
            for (int i = 0; i < 16 ; i++)
            {
                threadPool.execute(
                    new Thread(new ThreadPoolTest(), "Thread".concat(i + "")));
                System.out.println("线程池中活跃的线程数: " + threadPool.getPoolSize());
                if (queue.size() > 0)
                {
                    System.out.println("----------------队列中阻塞的线程数" + queue.size());
                }
            }
            threadPool.shutdown();
        }
    

    执行结果:

    线程池中活跃的线程数: 1
    线程池中活跃的线程数: 2
    线程池中活跃的线程数: 3
    线程池中活跃的线程数: 4
    线程池中活跃的线程数: 5
    线程池中活跃的线程数: 5
    ----------------队列中阻塞的线程数1
    线程池中活跃的线程数: 5
    ----------------队列中阻塞的线程数2
    线程池中活跃的线程数: 5
    ----------------队列中阻塞的线程数3
    线程池中活跃的线程数: 5
    ----------------队列中阻塞的线程数4
    线程池中活跃的线程数: 5
    ----------------队列中阻塞的线程数5
    线程池中活跃的线程数: 6
    ----------------队列中阻塞的线程数5
    线程池中活跃的线程数: 7
    ----------------队列中阻塞的线程数5
    线程池中活跃的线程数: 8
    ----------------队列中阻塞的线程数5
    线程池中活跃的线程数: 9
    ----------------队列中阻塞的线程数5
    线程池中活跃的线程数: 10
    ----------------队列中阻塞的线程数5
    Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task Thread[Thread15,5,main] rejected from java.util.concurrent.ThreadPoolExecutor@232204a1[Running, pool size = 10, active threads = 10, queued tasks = 5, completed tasks = 0]
        at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
        at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
        at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
        at test.ThreadTest.main(ThreadTest.java:17)
    

    从结果可以观察出:

    1、创建的线程池具体配置为:核心线程数量为5个;全部线程数量为10个;工作队列的长度为5。

    2、我们通过queue.size()的方法来获取工作队列中的任务数。

    3、运行原理:
    刚开始都是在创建新的线程,达到核心线程数量5个后,新的任务进来后不再创建新的线程,而是将任务加入工作队列,任务队列到达上线5个后,新的任务又会创建新的普通线程,直到达到线程池最大的线程数量10个,后面的任务则根据配置的饱和策略来处理。我们这里没有具体配置,使用的是默认的配置AbortPolicy:直接抛出异常。
      当然,为了达到我需要的效果,上述线程处理的任务都是利用休眠导致线程没有释放!!!

    5.RejectedExecutionHandler:饱和策略

    当队列和线程池都满了,说明线程池处于饱和状态,那么必须对新提交的任务采用一种特殊的策略来进行处理。这个策略默认配置是AbortPolicy,表示无法处理新的任务而抛出异常。JAVA提供了4中策略:

    1、AbortPolicy:直接抛出异常

    2、CallerRunsPolicy:只用调用所在的线程运行任务

    3、DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。

    4、DiscardPolicy:不处理,丢弃掉。

    我们现在用第四种策略来处理上面的程序:

    public static void main(String[] args)
        {
            LinkedBlockingQueue<Runnable> queue =
                new LinkedBlockingQueue<Runnable>(3);
            RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
    
            ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, queue,handler);
            for (int i = 0; i < 9 ; i++)
            {
                threadPool.execute(
                    new Thread(new ThreadPoolTest(), "Thread".concat(i + "")));
                System.out.println("线程池中活跃的线程数: " + threadPool.getPoolSize());
                if (queue.size() > 0)
                {
                    System.out.println("----------------队列中阻塞的线程数" + queue.size());
                }
            }
            threadPool.shutdown();
        }
    

    执行结果:

    线程池中活跃的线程数: 1
    线程池中活跃的线程数: 2
    线程池中活跃的线程数: 2
    ----------------队列中阻塞的线程数1
    线程池中活跃的线程数: 2
    ----------------队列中阻塞的线程数2
    线程池中活跃的线程数: 2
    ----------------队列中阻塞的线程数3
    线程池中活跃的线程数: 3
    ----------------队列中阻塞的线程数3
    线程池中活跃的线程数: 4
    ----------------队列中阻塞的线程数3
    线程池中活跃的线程数: 5
    ----------------队列中阻塞的线程数3
    线程池中活跃的线程数: 5
    ----------------队列中阻塞的线程数3
    

    这里采用了丢弃策略后,就没有再抛出异常,而是直接丢弃。在某些重要的场景下,可以采用记录日志或者存储到数据库中,而不应该直接丢弃。

    设置策略有两种方式:

    RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
     ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, queue,handler);
    
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, queue);
      threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
    

    6.Executors中的线程池的工厂方法

    为了防止使用者错误搭配ThreadPoolExecutor构造函数的各个参数以及更加方便简洁的创建ThreadPoolExecutor对象,JavaSE中又定义了Executors类,Eexcutors类提供了创建常用配置线程池的方法。以下是Executors常用的三个创建线程池的源代码。
    从源码中可以看出,Executors间接的调用了重载的ThreadPoolExecutor构造函数,并帮助用户根据不同的应用场景,配置不同的参数。

    1.newCachedThreadPool:使用SynchronousQueue作为阻塞队列,队列无界,线程的空闲时限为60秒。这种类型的线程池非常适用IO密集的服务,因为IO请求具有密集、数量巨大、不持续、服务器端CPU等待IO响应时间长的特点。服务器端为了能提高CPU的使用率就应该为每个IO请求都创建一个线程,以免CPU因为等待IO响应而空闲。

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

    2.newFixedThreadPool:需指定核心线程数,核心线程数和最大线程数相同,使用LinkedBlockingQueue 作为阻塞队列,队列无界,线程空闲时间0秒。这种类型的线程池可以适用CPU密集的工作,在这种工作中CPU忙于计算而很少空闲,由于CPU能真正并发的执行的线程数是一定的(比如四核八线程),所以对于那些需要CPU进行大量计算的线程,创建的线程数超过CPU能够真正并发执行的线程数就没有太大的意义。

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

    3.newSingleThreadExecutor:池中只有一个线程工作,阻塞队列无界,它能保证按照任务提交的顺序来执行任务。

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    

    4.newScheduleThreadPool:创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。

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

    相关文章
    ThreadLocal使用与原理

    更多相关内容
  • Java线程池原理讲解

    千次阅读 2022-01-21 15:03:18
    Java线程池核心原理讲解

    请添加图片描述

      本文给大家详细的来介绍下线程池的底层原理,希望对大家有所帮助。

    一、线程池原理

    1. 线程池优点

      线程池应该是Web容器中必不可少的组件了,因为每一个请求我们都需要通过对应的线程来处理,所以线程资源是非常重要的,如果管理不好系统的性能会急剧下降。所以重要性不言而喻。来看看它的有点吧。

    1. 线程是稀缺资源,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以重复使用。

    2. 可以根据系统的承受能力,调整线程池中工作线程的数量,防止因为消耗过多内存导致服务器崩溃。

    2.线程池的创建

      然后我们来看看线程池的创建方式,我们当然可以通过Executors提供的方法来创建,但是这种方式不推荐,实际开发中我们都会结合我们的业务需求来定制化对应的参数。
    在这里插入图片描述

    定制化参数,就是要看看ThreadPoolExecutor的构造方法

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

    参数含义:

    • corePoolSize:线程池核心线程数量
    • maximumPoolSize:线程池最大线程数量
    • keepAliverTime:当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间
    • unit:存活时间的单位
    • workQueue:存放任务的队列
    • handler:超出线程范围和队列容量的任务的处理程序

    3.线程池的实现原理

      提交一个任务到线程池中,线程池的处理流程如下:

    1. 判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。

    2. 线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。

    3. 判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

    在这里插入图片描述

    二、线程池源码

      然后我们来分析下线程池中的核心方法的源码。通过源码来加深对原理的理解。

    1.execute方法

      先来看ThreadPoolExecutor的execute()方法。

        public void execute(Runnable command) {
            if (command == null)
                throw new NullPointerException();
            //由它可以获取到当前有效的线程数和线程池的状态
            int c = ctl.get();
            // 1.获取当前正在运行线程数是否小于核心线程池,是则新创建一个线程执行任务,否则将任务放到任务队列中
            if (workerCountOf(c) < corePoolSize) { // 标识行 <------
                if (addWorker(command, true)) //在addWorker中创建工作线程执行任务
                    return;
                c = ctl.get();
            }
            // 2.当前核心线程池中全部线程都在运行workerCountOf(c) >= corePoolSize,所以此时将线程放到任务队列中
            if (isRunning(c) && workQueue.offer(command)) {
            		//线程池是否处于运行状态,且是否任务插入任务队列成功
                int recheck = ctl.get();
                if (! isRunning(recheck) && remove(command))
                    //线程池是否处于运行状态,如果不是则使刚刚的任务出队
                    reject(command); //抛出RejectedExceptionException异常
                else if (workerCountOf(recheck) == 0)
                    addWorker(null, false);
            }
            else if (!addWorker(command, false))
            // 3.插入队列不成功,且当前线程数数量小于最大线程池数量,此时则创建新线程执行任务,创建失败抛出异常
                reject(command);//抛出RejectedExceptionException异常
        }
    

      上面的标识行即判断当前核心线程池里是否有空闲线程,有则通过addWorker方法创建工作线程执行任务。addWorker方法较长,筛选出重要的代码来解析。

    2.addWorker

    private boolean addWorker(Runnable firstTask, boolean core) {
    /*首先会再次检查线程池是否处于运行状态,核心线程池中是否还有空闲线程,都满足条件过后则会调用compareAndIncrementWorkerCount先将正在运行的线程数+1,数量自增成功则跳出循环,自增失败则继续从头继续循环*/
      ...
      if (compareAndIncrementWorkerCount(c))
        break retry;
      ...
    /*正在运行的线程数自增成功后则将线程封装成工作线程Worker*/
      boolean workerStarted = false;
      boolean workerAdded = false;
      Worker w = null;
      try {
        final ReentrantLock mainLock = this.mainLock;        //全局锁
        w = new Woker(firstTask);        //将线程封装为Worker工作线程
        final Thread t = w.thread;
        if (t != null) {
          mainLock.lock();    //获取全局锁
    /*当持有了全局锁的时候,还需要再次检查线程池的运行状态等*/
          try {
            int c = clt.get();
            int rs = runStateOf(c);        //线程池运行状态
            if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)){        //线程池处于运行状态,或者线程池关闭且任务线程为空
              if (t.isAlive())    //线程处于活跃状态,即线程已经开始执行或者还未死亡,正确的应线程在这里应该是还未开始执行的
                throw new IllegalThreadStateException();
              workers.add(w);    //private final HashSet<Worker> wokers = new HashSet<Worker>();包含线程池中所有的工作线程,只有在获取了全局的时候才能访问它。将新构造的工作线程加入到工作线程集合中
              int s = worker.size();    //工作线程数量
              if (s > largestPoolSize)
                largestPoolSize = s;
              workerAdded = true;    //新构造的工作线程加入成功
            }
          } finally {
            mainLock.unlock();
          }
           if (workerAdded) {
            t.start();    //在被构造为Worker工作线程,且被加入到工作线程集合中后,执行线程任务,注意这里的start实际上执行Worker中run方法,所以接下来分析Worker的run方法
            workerStarted = true;
          }
        }
      } finally {
        if (!workerStarted)    //未能成功创建执行工作线程
          addWorkerFailed(w);    //在启动工作线程失败后,将工作线程从集合中移除
      }
      return workerStarted;
    }
    

    这里将线程封装成工作线程worker,并放入工作线程组里,worker类的方法run方法:

    3.Worker

    //ThreadPoolExecutor$Worker,它继承了AQS,同时实现了Runnable,所以它具备了这两者的所有特性
    private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
      final Thread thread;
      Runnable firstTask;
      public Worker(Runnable firstTask) {
        setState(-1);    
        //设置AQS的同步状态为-1,禁止中断,直到调用runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);   
         //通过线程工厂来创建一个线程,将自身作为Runnable传递传递
      }
      public void run() {
        runWorker(this);    //运行工作线程
      }
    }
    

    worker在执行完任务后,还会通过getTask方法循环获取工作队里里的任务来执行。

    三、线程池案例

     &esmp;我们通过一个具体的例子来加深下理解。

    1.创建线程

    public class ThreadPoolExample implements Runnable{
        @Override
        public void run() {
            try {
                Thread.sleep(200);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    

    2. 创建线程池

      在主方法中我们来完成测试。

        public static void main(String[] args) {
            LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(5);
            ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                    5
                    ,10
                    ,60
                    , TimeUnit.SECONDS
                    ,queue);
            for (int i = 0; i < 16; i++) {
                threadPool.execute(new Thread(new ThreadPoolExample(),"Thread".concat(i + "")));
                System.out.println("线程中活跃的线程数:" + threadPool.getPoolSize());
                if(queue.size() > 0){
                    System.out.println("---->队列中阻塞的线程数:" + queue.size());
                }
            }
            threadPool.shutdown();
        }
    

    3.执行结果

      执行后的效果如下,抛出了对应的异常信息。
    在这里插入图片描述

    从结果可以观察出:

    1. 创建的线程池具体配置为:核心线程数量为5个;全部线程数量为10个;工作队列的长度为5。
    2. 我们通过queue.size()的方法来获取工作队列中的任务数。
    3. 运行原理:

      刚开始都是在创建新的线程,达到核心线程数量5个后,新的任务进来后不再创建新的线程,而是将任务加入工作队列,任务队列到达上线5个后,新的任务又会创建新的普通线程,直到达到线程池最大的线程数量10个,后面的任务则根据配置的饱和策略来处理。我们这里没有具体配置,使用的是默认的配置AbortPolicy:直接抛出异常。

      当然,为了达到我需要的效果,上述线程处理的任务都是利用休眠导致线程没有释放!!!

    4.饱和策略

      当队列和线程池都满了,说明线程池处于饱和状态,那么必须对新提交的任务采用一种特殊的策略来进行处理。这个策略默认配置是AbortPolicy,表示无法处理新的任务而抛出异常。JAVA提供了4中策略:

    1. AbortPolicy:直接抛出异常
    2. CallerRunsPolicy:只用调用所在的线程运行任务
    3. DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
    4. DiscardPolicy:不处理,丢弃掉。

      我们现在用第四种策略来处理上面的程序:

        public static void main(String[] args) {
            // 阻塞队列
            LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(5);
            // 饱和策略
            RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
            ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                    5
                    ,10
                    ,60
                    , TimeUnit.SECONDS
                    ,queue
                    ,handler
            );
            for (int i = 0; i < 1000; i++) {
                threadPool.execute(new Thread(new ThreadPoolExample(),"Thread".concat(i + "")));
                System.out.println("线程中活跃的线程数:" + threadPool.getPoolSize());
                if(queue.size() > 0){
                    System.out.println("---->队列中阻塞的线程数:" + queue.size());
                }
            }
            threadPool.shutdown();
        }
    

    在这里插入图片描述

      这里采用了丢弃策略后,就没有再抛出异常,而是直接丢弃。在某些重要的场景下,可以采用记录日志或者存储到数据库中,而不应该直接丢弃。

    // 设置方式一
    RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, queue,handler);
    // // 设置方式二
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, queue);
    threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
    

    四、Callable、Future、FutureTash

      Callable与Future是在JAVA的后续版本中引入进来的,Callable类似于Runnable接口,实现Callable接口的类与实现Runnable的类都是可以被线程执行的任务。

    1.三者之间的关系

    • Callable是Runnable封装的异步运算任务。
    • Future用来保存Callable异步运算的结果
    • FutureTask封装Future的实体类

    Callable与Runnbale的区别:
    a. Callable定义的方法是call,而Runnable定义的方法是run。
    b. call方法有返回值,而run方法是没有返回值的。
    c. call方法可以抛出异常,而run方法不能抛出异常。

    2.Future

      Future表示异步计算的结果,提供了以下方法,主要是判断任务是否完成、中断任务、获取任务执行结果.

    public interface Future<V> {
    
        boolean cancel(boolean mayInterruptIfRunning);
    
        boolean isCancelled();
    
        boolean isDone();
    
        V get() throws InterruptedException, ExecutionException;
    
        V get(long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException;
    }
    

    3.FutureTask

      可取消的异步计算,此类提供了对Future的基本实现,仅在计算完成时才能获取结果,如果计算尚未完成,则阻塞get方法。

    public class FutureTask<V> implements RunnableFuture<V>
    public interface RunnableFuture<V> extends Runnable, Future<V>
    

      FutureTask不仅实现了Future接口,还实现了Runnable接口,所以不仅可以将FutureTask当成一个任务交给Executor来执行,还可以通过Thread来创建一个线程。

    4.Callable与FutureTask案例

    创建一个Callable接口

     public class MyCallableTask implements Callable<Integer>
     {
         @Override
         public Integer call()
             throws Exception
         {
             System.out.println("callable do somothing");
             Thread.sleep(5000);
             return new Random().nextInt(100);
         }
     }
    

    创建main方法

    public static void main(String[] args) throws Exception
    {
        Callable<Integer> callable = new MyCallableTask();
        FutureTask<Integer> future = new FutureTask<Integer>(callable);
        Thread thread = new Thread(future);
        thread.start();
        Thread.sleep(100);
        //尝试取消对此任务的执行
        future.cancel(true);
        //判断是否在任务正常完成前取消
        System.out.println("future is cancel:" + future.isCancelled());
        if(!future.isCancelled())
        {
            System.out.println("future is cancelled");
        }
        //判断任务是否已完成
        System.out.println("future is done:" + future.isDone());
        if(!future.isDone())
        {
            System.out.println("future get=" + future.get());
        }
        else
        {
            //任务已完成
            System.out.println("task is done");
        }
    }
    

    运行结果

    callable do somothing
    future is cancel:true
    future is done:true
    task is done
    

    5.Callable与Future

    public class CallableThread implements Callable<String>
    {
        @Override
        public String call()
            throws Exception
        {
            System.out.println("进入Call方法,开始休眠,休眠时间为:" + System.currentTimeMillis());
            Thread.sleep(10000);
            return "今天停电";
        }
        
        public static void main(String[] args) throws Exception
        {
            ExecutorService es = Executors.newSingleThreadExecutor();
            Callable<String> call = new CallableThread();
            Future<String> fu = es.submit(call);
            es.shutdown();
            Thread.sleep(5000);
            System.out.println("主线程休眠5秒,当前时间" + System.currentTimeMillis());
            String str = fu.get();
            System.out.println("Future已拿到数据,str=" + str + ";当前时间为:" + System.currentTimeMillis());
        }
    }
    

    执行结果

    进入Call方法,开始休眠,休眠时间为:1478606602676
    主线程休眠5秒,当前时间1478606608676
    Future已拿到数据,str=今天停电;当前时间为:1478606612677
    

      这里的future是直接扔到线程池里面去执行的。由于要打印任务的执行结果,所以从执行结果来看,主线程虽然休眠了5s,但是从Call方法执行到拿到任务的结果,这中间的时间差正好是10s,说明get方法会阻塞当前线程直到任务完成。

      通过FutureTask也可以达到同样的效果.

    public static void main(String[] args) throws Exception
        {
          ExecutorService es = Executors.newSingleThreadExecutor();
          Callable<String> call = new CallableThread();
          FutureTask<String> task = new FutureTask<String>(call);
          es.submit(task);
          es.shutdown();
          Thread.sleep(5000);
          System.out.println("主线程等待5秒,当前时间为:" + System.currentTimeMillis());
          String str = task.get();
          System.out.println("Future已拿到数据,str=" + str + ";当前时间为:" + System.currentTimeMillis());
        }
    

    以上的组合可以给我们带来这样的一些变化:

    如有一种场景中,方法A返回一个数据需要10s,A方法后面的代码运行需要20s,但是这20s的执行过程中,只有后面10s依赖于方法A执行的结果。如果与以往一样采用同步的方式,势必会有10s的时间被浪费,如果采用前面两种组合,则效率会提高:

    1. 先把A方法的内容放到Callable实现类的call()方法中

    2. 在主线程中通过线程池执行A任务

    3. 执行后面方法中10秒不依赖方法A运行结果的代码

    4. 获取方法A的运行结果,执行后面方法中10秒依赖方法A运行结果的代码

    这样代码执行效率一下子就提高了,程序不必卡在A方法处。

    好了线程池的内容就给大家讲解到这里了。

    展开全文
  • java线程池原理

    2021-03-09 19:49:04
    参考文章:作者:AKyS佐毅链接:https://www.jianshu.com/p/f5ead62827d7来源:简书线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。它把T1,T3分别安排在服务器程序的启动和结束的...

    参考文章:

    作者:AKyS佐毅

    链接:https://www.jianshu.com/p/f5ead62827d7

    来源:简书

    线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1,T3的开销了。

    个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。

    如果:T1 + T3 远大于 T2(节省创建/销毁的时间),则可以采用线程池,以提高服务器性能。另外发挥多核CPU的优势。

    假设一个服务器一天要处理50000个请求,并且每个请求需要一个单独的线程完成。在线程池中,线程数一般是固定的,所以产生线程总数不会超过线程池中线程的数目,而如果服务器不利用线程池来处理这些请求则线程总数为50000。一般线程池大小是远小于50000。所以利用线程池的服务器程序不会为了创建50000而在处理请求时浪费时间,从而提高效率

    java.util.concurrent.Executors提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池

    ExecutorService pool = Executors.newFixedThreadPool(6);

    一个线程池包括以下四个基本组成部分:

    1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;

    2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;

    3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;

    4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

    主要特点:

    线程复用

    控制最大并发数量

    管理线程

    继承关系:

    8fe7b43c96203024f02d86547660eba7.png

    创建线程池:

    00193ac702e209311e4ddb3cb7bba40b.png

    创建线程池的代码:

    a39995f81c1bb928904b5b0a554ee9a4.png

    关键参数代码的意义:对于

    newFixedThreadPool/newSingleThreadExecutor中,corePoolSize == maximumPoolSize;

    newCachedThreadPool中,maximumPoolSize = Inter.MAX_VALUE.

    如果上述Pool不能满足需求,可以在自己的代码中直接调用ThreadPoolExecuto

    keepAliveTime默认是0;

    WorkQueue: 阻塞队列,放临时任务。

    8735a0fd433dbac8132a95b2287cfd66.png

    线程池的处理流程:

    31fab755182185c34599c7c46d7d4c47.png

    2906838bdf9d2c8fdb757edf02b27491.png

    4、四种拒绝策略

    ThreadPoolExecutor.AbortPolicy():抛出java.util.concurrent.RejectedExecutionException异常

    ThreadPoolExecutor.CallerRunsPolicy: 用于被拒绝任务的处理程序,它直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务

    ThreadPoolExecutor.DiscardOldestPolicy():该策略将丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。

    ThreadPoolExecutor.DiscardPolicy:用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。

    37245b2088f9f22d8da6fdc16b863f12.png

    f29819627bed91523d929d6d04234f89.png

    展开全文
  • 主要介绍了JAVA线程池原理,结合实例形式详细分析了java线程池概念、原理、创建、使用方法及相关注意事项,需要的朋友可以参考下
  • Java线程池原理和使用

    2021-03-19 09:20:04
    什么是线程池? 线程池是Java线程的一种使用模式,通过池的思想对线程的创建和使用进行统一的管理。...线程池原理 线程池使用了池化技术,核心思想是把稀缺资源放到一个池子中进行统一管理,需要使用时从池
    什么是线程池?

    线程池是Java线程的一种使用模式,通过池的思想对线程的创建和使用进行统一的管理。

    为什么要用线程池?

    Java线程是稀缺资源,频繁的创建和销毁会对CPU带来一定的开销,线程过多也会带来调度开销,不易维护和管理,进而影响缓存局部性和整体的性能。使用线程池可以对线程进行复用,避免了在处理短时间任务时创建与销毁线程的代价,还能防止过分调度,线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。

    线程池原理

    线程池使用了池化技术,核心思想是把稀缺资源放到一个池子中进行统一管理,需要使用时从池中获取,使用结束后再放回到池中,供其他人使用。
    了解了线程池的核心思想之后,我们一起来看看Java如何实现线程池。

    1. 线程池的创建

    Java创建线程池的方式主要有两种,一种是通过JDK1.5推出的Excutors类的静态方法进行创建,通过该类可以创建三种不同类型的线程池
    (1)创建固定大小的线程池
    在这里插入图片描述
    (2)创建单个线程的线程池
    在这里插入图片描述
    (3)创建无限大小的线程池
    在这里插入图片描述
    使用Excutors静态方法创建线程池的优点是方便简单,而缺点也很明显,那就是不容易对线程池进行扩展。
    另一种创建线程池的方式是直接使用ThreadPoolExecutor类创建
    在这里插入图片描述
    ThreadPoolExecutor构造器具备如下几个核心参数:

    • corePoolSize:线程池的核心线程数量。
    • maximumPooSize:线程池的最大线程数量。
    • keepAliveTime 和 unit: 线程池空闲存活时间和时间单位。
    • workQueue:存放任务的阻塞队列。
    • handler:当任务队列和最大线程数都满了之后所执行的拒绝策略。
    线程池的工作流程

    通过调用线程池的execute(Runnable r) 传递一个任务到线程池中,线程池接收到任务后,首先判断当前线程池的核心线程数是否已经达到最大值,如果未达到则创建一个线程执行该任务,反之判断当前任务队列是否已满,如果任务队列未满的话将该任务加入到任务队列中,等待后期的任务调度。否则再去判断当前线程池的总线程数是否达到最大值,如果未达到,创建一个线程执行任务。而如果核心线程数、任务队列、总线程数都达到饱和后、此时线程池无法在接收新的任务,将会执行拒绝策略,拒绝该任务进入到线程池中。
    在这里插入图片描述
    线程池中定义了物种线程状态,这些状态和线程的执行密切相关。
    在这里插入图片描述
    - RUNNING 运行状态,可以接收用户提交的任务和执行任务队列里的人物
    - SHUTDOWN 调用shutdown()方法后,不再接收新任务,但需要将队列里的任务全部执行完。
    - STOP 调用了shutdownNow()方法后,不再接收新任务,同时抛弃任务队列里的所有任务。
    - TIDYING 在调用shutdown() 或 shutdownNow()方法后会尝试更新为当前状态,处于当前状态下线程池中执行的任务为空。
    - TERMINATED 终止状态,当执行terminated()后会更新=为该状态。
    状态转换路程如下
    在这里插入图片描述

    线程池的使用
    ThreadPoolExecutor pool = new ThreadPoolExecutor(10, 20, 30,
             TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
    
            for (int i = 0; i < 20; i++) {
                pool.execute(new Runnable(){
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName() + "正在运行");
                    }
                });
                
            }
    
    展开全文
  • 主要介绍了Java 线程池原理深入分析的相关资料,需要的朋友可以参考下
  • Java线程池原理

    2020-03-28 20:54:33
    Java线程理解:   线程是调度CPU的最小单元,也叫轻量级进程LMP(Light Weight Process)。 两种线程模型: 用户级线程(ULT):用户程序实现,不依赖操作系统核心,应用提供创建、同步、调度和管理线程的函数来...
  • 主要介绍了Java常用线程池原理及使用方法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 文章目录什么是线程池使用线程池的好处线程池的实现原理流程图分析源码分析线程池的使用 什么是线程池 线程池就是提前创建若干个线程,如果有任务需要处理,线程池里的线程就会处理任务,处理完之后线程并不会被...
  • 由浅入深Java线程池原理解析

    万次阅读 2020-05-12 19:29:47
    Java线程池原理解析 前言 线程是稀缺资源,如果被无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,合理的使用线程池对线程进行统一分配、调优和监控,有以下好处: 1、降低资源消耗;通过重复利用已创建好...
  • JAVA线程池原理详解一

    千次阅读 2021-03-02 10:55:19
    线程池的优点1、线程是稀缺资源,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以重复使用。2、可以根据系统的承受能力,调整线程池中工作线程的数量,防止因为消耗过多内存导致服务器崩溃。线程池的...
  • 图解Java线程池原理

    千次阅读 2019-08-21 10:11:49
    什么是线程池? 为了避免频繁重复的创建和销毁线程,我们可以让这些线程进行复用,在线程池中,总会有活跃的线程在占用,但是线程池中也会存在没有占用的线程,这些线程处于空闲状态,当有任务的时候会从池子里面拿...
  • 通俗易懂的Java线程池原理
  • jdk在java5版本中增加了内置线程池实现ThreadPoolExecutor,本文通过ThreadPoolExecutor的源码分析jdk中线程池的实现原理线程池由两个核心数据结构组成: 1)线程集合(workers):存放执行任务的线程,是一个...
  • Java线程池原理分析

    2019-03-31 20:52:34
    线程池设计理念1.1 减少创建/切换线程开销1.2 线程池模型1.2.1 BlockingQueue1.2.2 corePool1.2.3 maxmumPool1.2.4 RejectedExecutionHandler1.2.3 execute2.线程池使用实例3. 线程池工作流程 其实这篇文章在去年就...
  • 浅谈Java 线程池原理及使用方式

    千次阅读 2020-04-26 16:40:45
    java中有线程池、连接池等等。线程池就是在系统启动或者实例化池时创建一些空闲的线程,等待工作调度,执行完任务后,线程并不会立即被销毁,而是重新处于空闲状态,等待下一次调度。 线程池的工作机制? 在线程池...
  • 文章目录java线程池使用简介前言一、线程池二、线程池底层工作原理2.线程池的7大参数总结 前言 java中,创建线程有四种方法:(1)继承Thread类 (2)实现Runnable接口 (3)实现callable接口 (4)使用线程池 本文...
  • 深度解析Java 线程池的实现原理

    千次阅读 2021-05-26 10:26:17
    Java 系统的运行归根到底是程序的运行,程序的运行归根到底是代码的执行,代码的执行归根到底是虚拟机的执行,虚拟机的执行其实就是操作系统的线程在执行,并且会占用一定的系统资源,如CPU、内存、磁盘、网络等等。...
  • Java线程池原理及使用

    万次阅读 多人点赞 2018-09-27 17:10:08
    java中的线程池是运用场景最多的并发框架。在开发过程中,合理的使用线程池能够带来下面的一些好处: 1、降低资源的消耗。 2、提高响应速度。 3、提高线程的可管理型。 1.1、线程池ThreadPoolExecutor工作原理 讲解...
  • 一、线程池简介:多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中...
  • 在什么情况下使用线程池?  1.单个任务处理的时间比较短  2....  使用线程池的好处:  1.... 2.如不使用线程池,有可能造成系统创建大量线程而导致消耗完...线程池工作原理: 为什么要用线程池? 诸如 Web 服
  • Java线程池实现原理详解

    万次阅读 多人点赞 2018-03-16 21:57:32
    其实java线程池的实现原理很简单,说白了就是一个线程集合workerSet和一个阻塞队列workQueue。当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。workerSet中的线程会不断的从workQueue...
  • Java面试题之:线程池原理一、简介二、线程复用三、线程池的组成四、拒绝策略五、Java 线程池工作过程 一、简介   线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动...
  • Java线程池工作原理

    万次阅读 多人点赞 2018-11-10 01:08:13
    Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池,所以我们就要认识并弄懂线程池,以便于更好的为我们业务场景服务。 一、线程池的好处 在开发过程中,合理地...
  • Java 线程池原理和队列详解 线程池的框架图: 一、ThreadPoolExecutor线程池实现类 ThreadPoolExecutor是线程池的核心类。首先看一下如何创建一个ThreadPoolExecutor。下面是ThreadPoolExecutor常用的一个构造...
  • 随着cpu核数越来越多,不可避免的利用多线程技术以充分...在Java用有一个Executors工具类,可以为我们创建一个线程池,其本质就是new了一个ThreadPoolExecutor对象。线程池几乎也是面试必考问题。本文结合源代码,...
  • 本文介绍Java线程池原理,包括:线程池的流程、线程池的结构、线程池的任务状态。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 115,393
精华内容 46,157
关键字:

java线程池原理