线程以及线程池

Posted notchangeworld

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程以及线程池相关的知识,希望对你有一定的参考价值。

多线程:

线程池的创建:

一、使用ThreadPoolExecutor类

二、使用Executors

注:两种本质一样,都是通过ThreadPoolExecutor类的方式。

 

ThreadPoolExecutor方式

jdk源码:


public ThreadPoolExecutor(int corePoolSize,
                             int maximumPoolSize,
                             long keepAliveTime,
                             TimeUnit unit,
                             BlockingQueue<Runnable> workQueue) {
       this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
            Executors.defaultThreadFactory(), defaultHandler);
  }
?
public ThreadPoolExecutor(int corePoolSize,
                             int maximumPoolSize,
                             long keepAliveTime,
                             TimeUnit unit,
                             BlockingQueue<Runnable> workQueue,
                             ThreadFactory threadFactory) {
       this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
            threadFactory, defaultHandler);
  }
?
public ThreadPoolExecutor(int corePoolSize,
                             int maximumPoolSize,
                             long keepAliveTime,
                             TimeUnit unit,
                             BlockingQueue<Runnable> workQueue,
                             RejectedExecutionHandler handler) {
       this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
            Executors.defaultThreadFactory(), handler);
  }
?
?
public ThreadPoolExecutor(int corePoolSize,
                             int maximumPoolSize,
                             long keepAliveTime,
                             TimeUnit unit,
                             BlockingQueue<Runnable> workQueue,
                             ThreadFactory threadFactory,
                             RejectedExecutionHandler handler) {
       if (corePoolSize < 0 ||
           maximumPoolSize <= 0 ||
           maximumPoolSize < corePoolSize ||
           keepAliveTime < 0)
           throw new IllegalArgumentException();
       if (workQueue == null || threadFactory == null || handler == null)
           throw new NullPointerException();
       this.acc = System.getSecurityManager() == null ?
               null :
               AccessController.getContext();
       this.corePoolSize = corePoolSize;
       this.maximumPoolSize = maximumPoolSize;
       this.workQueue = workQueue;
       this.keepAliveTime = unit.toNanos(keepAliveTime);
       this.threadFactory = threadFactory;
       this.handler = handler;
  }
?

参数说明:

corePoolSize:线程池的核心线程数;当线程数少于corePoolSize的时候,直接创建新的线程,尽管其他线程是空闲的。当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;

maximumPoolSize:线程池的最大线程数;如果线程数量少于线程最大数且大于核心线程数量的时候,只有当阻塞队列满了才创建新线程。当线程数量大于最大线程数且阻塞队列满了这时候就会执行一些策略来响应该线程。

keepAliveTime:线程池空闲时线程的存活时长;也就是当线程数量超过核心线程数量的时候,且小于最大线程数量,这一部分的线程在没有任务执行的时候是会保持直到超过keepAliveTime才会销毁

unit:线程存活时长大单位,结合上个参数使用;

workQueue:存放任务的队列,使用的是阻塞队列。阻塞队列,存储等待执行的任务,会对线程池的运行产生很大的影响。当提交一个新的任务到线程池的时候,线程池会根据当前线程数量来选择不同的处理方式

直接切换队列SynchronousQueue:该队列传递任务到线程而不持有它们。在这一点上,试图向该队列压入一个任务,如果没有可用的线程立刻运行任务,那么就会入列失败,所以一个新的线程就会被创建。当处理那些内部依赖的任务集合时,这个选择可以避免锁住。直接接传递通常需要无边界的最大线程数来避免新提交任务被拒绝处理。当任务以平均快于被处理的速度提交到线程池时,它依次地确认无边界线程增长的可能性

使用无界队列LinkedBlockingQueue:使用这个队列的话,没有预先定义容量的无界队列,最大线程数是为corePoolSize,在核心线程都繁忙的时候会使新提交的任务在队列中等待被执行,所以将不会创建更多的线程,这时候,maximunPoolSize最大线程数的值将不起作用。当每个任务之间是相互独立的时比较适合该队列,任务之间不能互相影响执行。

使用有界队列ArrayBlockingQueue:使用这个队列,线程池中的最大线程数量就是maximunPoolSize,能够降低资源消耗,但是却使得线程之间调度变得更加困难,因为队列容量和线程池都规定完了。

如果想降低系统资源消耗,包括CPU使用率,操作系统资源消耗,上下文切换开销等等,可以设置一个较大的队列容量,较小的maximunPoolSize。如果线程经常发生阻塞,那么可以稍微将maximunPoolSize设置大一点

threadFactory:线程池创建线程的工厂;当使用默认的线程工厂创建线程的时候,会使得线程具有相同优先级,并且设置了守护性,同时也设置线程名称

handler:在队列(workQueue)和线程池达到最大线程数(maximumPoolSize)均满时仍有任务的情况下的处理方式。拒绝策略。当workQueue满了,并且没有空闲的线程数,即线程达到最大线程数。就会有四种不同策略来处理

AbortPolicy(默认):丢弃任务并抛出RejectedExecutionException异常。

CallerRunsPolicy:由调用线程处理该任务。(例如io操作,线程消费速度没有NIO快,可能导致阻塞队列一直增加,此时可以使用这个模式)

DiscardPolicy:丢弃任务,但是不抛出异常。 (可以配合这种模式进行自定义的处理方式)

DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务(重复执行)

线程池的状态:

技术图片

RUNNING:在这个状态的线程池能判断接受新提交的任务,并且也能处理阻塞队列中的任务

SHUTDOWN:处于关闭的状态,该线程池不能接受新提交的任务,但是可以处理阻塞队列中已经保存的任务,在线程处于RUNNING状态,调用shutdown()方法能切换为该状态。

STOP:线程池处于该状态时既不能接受新的任务也不能处理阻塞队列中的任务,并且能中断现在线程中的任务。当线程处于RUNNING和SHUTDOWN状态,调用shutdownNow()方法就可以使线程变为该状态

TIDYING:在SHUTDOWN状态下阻塞队列为空,且线程中的工作线程数量为0就会进入该状态,当在STOP状态下时,只要线程中的工作线程数量为0就会进入该状态。

TERMINATED:在TIDYING状态下调用terminated()方法就会进入该状态。可以认为该状态是最终的终止状态。

 

通过Executors

1、Executors.newCacheThreadPool();按需创建新的线程,先查看池中有没有以前建立线程,如果有,就重用,如果没有,就创建一个新的线程加入池中,通常用于存期很短的任务;


public class ThreadPoolExecutorTest {
    public static void main(String[] args) {
         //创建一个线程池
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
        Thread.sleep(1000);//每隔一秒创建一个线程
            cachedThreadPool.execute(new Runnable() {
                public void run() {
                //打印正在执行的缓存线程信息
                System.out.println(Thread.currentThread().getName()+"正在被执行");
                }
          });
        }
    }
}

2、Executors.newFixedThreadPool(int n):创建一个线程池,该线程池可并发执行的线程数固定,某一线程执行完当前任务后该线程可以被重用执行另外一任务,线程池n个线程都被占用时,其他任务按照任务的提交顺序在队列中等待;

public class ThreadPoolExecutorTest {
      public static void main(String[] args) {
       //创建一个可重用固定个数的线程池
       ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
       for (int i = 0; i < 10; i++) {
                fixedThreadPool.execute(new Runnable() {      //很厉害的匿名内部类
                public void run() {
                try {
                  //打印正在执行的缓存线程信息
                 System.out.println(Thread.currentThread().getName()+"正在被执行");
                        Thread.sleep(2000);     //每隔2秒轮流打印3个线程的名字
                    } catch (InterruptedException e) {
                       e.printStackTrace();
                    }
                }
            });
        }
    }
}

3、Executors.newSingleThreadExecutor():创建一个只有单线程的线程池、它只会用唯一的线程来执行任务,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它,此线程池保证所有任务的执行顺序按照任务的提交顺序执行

public class TestThreadPoolExecutor {
     public static void main(String[] args) {
         //创建一个单线程化的线程池
         ExecutorService singleThreadExecutor =  
                       Executors.newSingleThreadExecutor();
         for (int i = 0; i < 10; i++) {
           /*index为不可变量,每一次for循环后本次final变量出了这个块的作用域范围就            
             会失效,下次循环的时候重建一个不同域的final变量,故不同域对应的线程使用
             不同的index*/
            final int index = i;
            singleThreadExecutor.execute(new Runnable() {
            public void run() {
            try {
                 //结果依次输出,相当于顺序执行各个任务
                  System.out.println(Thread.currentThread().getName()+"正在被执
                                               行,打印的值是:"+index);
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}      

4、Executors.newScheduledThreadPool(int n):创建一个定长线程池,线程池里面的任务又分为两种,定时或者周期性执行

定时执行:
public class ThreadPoolExecutorTest {
     public static void main(String[] args) {
         //创建一个定长线程池,支持定时及周期性任务执行——延迟执行
        ScheduledExecutorService scheduledThreadPool =    
        Executors.newScheduledThreadPool(5);
        //延迟1秒执行
        scheduledThreadPool.schedule(new Runnable() {
            public void run() {
                System.out.println("延迟1秒执行");
          }
        }, 1, TimeUnit.SECONDS);//1秒后执行一次即over
    }
}    
周期性执行
public class ThreadPoolExecutorTest {
        public static void main(String[] args) {
         //创建一个定长线程池,支持定时及周期性任务执行——定期执行
        ScheduledExecutorService scheduledThreadPool =    
                           Executors.newScheduledThreadPool(5);
        /*延迟1秒后执行该任务,以后每隔3秒再次执行该任务,每隔3秒若上次任务已完成则
           启动本次任务,否则等上次任务结束后才启动本次任务(尽管已经到了3秒)*/
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
        public void run() {
                System.out.println("延迟1秒后再每3秒执行一次");
            }
        }, 1, 3, TimeUnit.SECONDS);
    }
}            

 

but,在阿里巴巴开发手册中提到,使用Executors创建线程池可能会导致OOM。

FixedThreadPool 和 SingleThreadExecutor: 允许请求的队列长度为 Integer.MAX_VALUE ,可能堆积大量的请求,从而导致OOM。

CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM

(Integer.MAX_VALUE = 2147483647)

原因为LinkedBlockingQueue.offer

其中,Java中的BlockingQueue主要有两种实现,分别是ArrayBlockingQueue 和 LinkedBlockingQueue。

ArrayBlockingQueue是一个用数组实现的有界阻塞队列,必须设置容量。

LinkedBlockingQueue是一个用链表实现的有界阻塞队列,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE。

这里的问题就出在:不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE。也就是说,如果我们不设置LinkedBlockingQueue的容量的话,其默认容量将会是Integer.MAX_VALUE。

而newFixedThreadPool中创建LinkedBlockingQueue时,并未指定容量。此时,LinkedBlockingQueue就是一个无边界队列,对于一个无边界队列来说,是可以不断的向队列中加入任务的,这种情况下就有可能因为任务过多而导致内存溢出问题

 

所以,如何正确的创建一个想线程池呢???

1、自己创建


private static ExecutorService executor = new ThreadPoolExecutor(10,10,60L,TimeUnit.SECONDS,new ArrayBlockingQueue(20));

以上这种情况,一旦提交的线程数超过当前可用线程数,就会抛出java.util.concurrent.RejectedExecutionException,这是因为当前线程池使用的队列是有边界队列,队列已经满了无法继续处理新的请求;但是这个异常可以捕获,然后处理。

2、利用开源类库

使用guava提供的ThreadFactoryBuilder来创建线程池


private static ThreadFactory namedThreadFactory = new ThreadFactoryBuild.setNameFormat("demo-pool-%d").build();
?
private static ExecutorService pool = new ThreadPoolExecutor(5,200,0L,TimeUnit.MILLSECONDS,new LinkedBlockingQueue<Runnable>(1024),namedThreadFactory,new ThreadPoolExecutor.AbortPolicy());

 

 

 

 

创建线程的方法3种;

实现Runnable接口

继承Thread类

实现Callable接口

实现Runnable接口和Callable的区别

如果想让线程池执行任务的话需要实现的Runnable接口或Callable接口。 Runnable接口或Callable接口实现类都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。两者的区别在于 Runnable 接口不会返回结果但是 Callable 接口可以返回结果。

备注: 工具类Executors可以实现Runnable对象和Callable对象之间的相互转换。(Executors.callable(Runnable task)或Executors.callable(Runnable task,Object resule))。

执行execute()方法和submit()方法的区别是什么?

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

submit() 方法用于提交需要返回值的任务。线程池会返回一个Future类型的对象,通过这个Future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。

 

 部分内容有转载,如有侵权,请告知,立删;

以上是关于线程以及线程池的主要内容,如果未能解决你的问题,请参考以下文章

Java线程池详解

Java——线程池

Java线程池详解

Java线程池详解

Java 线程池详解

Motan在服务provider端用于处理request的线程池