java线程池详解

java多线程关于ThreadPool

一、自定义ThreadFactory和线程池的工厂类Executors

自定义ThreadFactory,可以自定义线程的名字

public class MyThreadFactory implements ThreadFactory {

    private final String prefix = "async-task-";
    private final AtomicInteger threadNumber = new AtomicInteger(1);

    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(null,r,prefix + threadNumber.getAndIncrement());
        return thread;
    }
}

Executors可以用来创建线程池,但是不太推荐。
可以使用ThreadPoolExecutor来自定义线程池,来适用当前业务

Executors.newCachedThreadPool:

创建的是无界线程池(可以创建的线程数量是Integer.MAX_VALUE),缺点:高并发下内存占用率大幅升高,导致内存溢出或者系统性能严重下降。

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

创建时需要传入核心线程池的线程数,并使用LinkedBlockingQueue作为排队队列,创建的线程数是有限的。

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

核心线程只有一个,其它提交的线程全部放入队列等待执行。

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

二、ThreadPoolExecutor类的参数和使用

2.1、参数介绍:

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                KeepaliveTime,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(2),
                new MyThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

corePoolSize: 线程池中至少要保留的线程,核心线程默认是不会超时销毁的。pool.allowCoreThreadTimeOut(true),可以开启核心线程超时销毁。

maximumPoolSize:可创建的最大线程数。

KeepaliveTime:当线程数量大于corePoolSize,没有超过指定的时间是不能从线程池中将线程删除的。范围是maximumPoolSize~corePoolSize之间的线程。是核心线程之外的线程

unit:KeepaliveTime参数的时间单位

线程队列:
1、使用linkedBlockingQueue做队列,linkedBlockingQueue是有界的,默认Integer.MAX_VALUE
2、ArrayBlockingQueue队列。实例化时必须传入初始容量,并且容量不可扩充,超出初始容量会报错。
3、SynchronousQueue队列,该队列不存储数据,通过该队列,可以在两个线程之间传递数据

WorkQueue:执行任务的任务队列,保存有executor提交的任务

2.2、线程池的队列类型

使用 LinkedBlockingQueue<>()无界队列的情况

1、对于存在corePoolSIze和无参new LinkedBlockingQueue<>()队列的情况下,无论提交了多少个任务,线程池中的线程数都不会超过corePoolSize。

2、对于提交的线程数大于corePoolSIze的情况,只会创建corePoolSIze个线程,其他的任务全部放入队列中的等待执行。

使用 LinkedBlockingQueue<>(n)有界队列的情况

1、对于提交的线程数大于maximumPoolSize + n 的情况,线程池中会立即创建并不多于maximumPoolSize个线程来执行任务,多于maximumPoolSize+ n的额外任务会被拒绝并抛出异常。

使用SynchronousQueue<>()的情况

1、如果提交的线程数量大于corePoolSize,而小于等于maximumPoolSize,则创建最多不超过maximumPoolSize个线程来执行任务,如果空闲线程存活时间超过keepaliveTime,则超过corePoolSize之外的线程会被杀死,其他线程依然存活。

2、如果提交任务数量大于maximumPoolSize,则抛出异常。

2.3、线程池的拒绝策略

1、AbortPolicy

当任务添加到线程池被拒绝时,直接抛出RejectedExceutionException异常,是线程池的默认策略。

2、CallerRunsPolicy

被拒绝后,会被加入到调用线程池的线程去处理,可能会造成主线程的阻塞。
比较好的处理方法是在主线程中创建一个线程,并由该线程负责线程池的调用。

3、DiscardOldestPolicy

当任务添加线程池被拒绝时,线程池会放弃等待队列中最旧的未处理任务,然后将被拒绝的任务添加到等待队列。

4、Discardpolicy

当任务添加到线程池中被拒绝时,线程池会丢弃被拒绝的任务。

三、ThreadPoolExecutor 其他的一些方法

afterExecute()和beforeExecute()

重写这两个方法可以对线程池的线程对象实现监控。

remove()方法

对于execute 提交的任务,remove方法可以删除未运行的thread,但是对于正在运行的thread无法删除。

对于submit 提交的任务,remove方法不能删除。

execute方法: 返回void,只能提交Runnable类型任务

submit方法: 可以提交Runnable和Callable类型的任务,当提交Runnable时返回值是null,提交的类型是callable时,返回的类型是Future。

submit和submit方法对于异常处理的区别:

submit方法是是捕获了异常的,当调用FutureTask的get方法时,才会抛出异常。运行时出现异常不会抛出。

但是execute方法,在运行时有异常就会抛出。

四、工作队列、线程池大小的选择

工作队列

工作队列可以使用有界队列,也可以使用无界队列,或者直接交接队列(SynchronousQueue)。

对于无界队列,虽然无界队列本身不限制线程池任务数,但是无界队列的容量还是取决于任务本身对资源的使用情况。如果创建任务时引用了其他的对象,而这些对象又特别大,随着队列中任务越多,会导致这些对象占用的内存也越来越多。极端情况下可能会导致内存溢出。

可以对队列的大小做出限制,或者使用直接交接队列SynchronousQueue,那么便会创建不多于最大线程数的线程,且又不会无限的缓存任务。

线程池大小

cpu密集型:

可以考虑N+1,考虑+1是可能会有缺页中断,导致线程等待。

io密集型:

可以设置的相对大一点,因为等待io的时间会比使用cpu的时间长,处于io等待状态的线程并不会消耗cpu资源,例如2n。

对于执行io操作的线程越多,引起上下文切换也就越多,因此对于io密集型的任务,核心线程数可以设为1,最大线程数可以设为2N,这样导致的上下文切换也是最少的。

五、线程泄露

run方法没有异常捕获,导致run方法意外返回,使线程提前终止。

或者run方法一直处于等待状态,没有设置超时。

六、队列

6.1 非阻塞队列

当队列为空时返回异常或者null
ConcurrentHashMap:支持并发操作的map,不支持排序。

ConcurrentSkipListMap:支持并发操作,且支持排序。需要对象实现Comparable接口,并重写compareTo方法,实现排序,返回-1表示小,返回1表示大,返回0表示相等。

ConcurrentSkipListSet:支持并发和排序,且不允许数据重复。需要对象实现Comparable接口,并重写compareTo方法,实现排序,返回-1表示小,返回1表示大,返回0表示相等。重写hashCode方法,和equals。

ConcurrentLinkedQueue:提供了并发的队列,仅支持对头的操作。

ConcurrentLinkedDequeue:支持对头尾的双向操作。

CopyOnWriteArrayList:由于ArrayList是线程不安全的,可以使用它代替。

CopyOnWriteArraySet:可以解决HashSet不安全的问题。

6.2阻塞队列

当队列为空,或者队列的容量满的时候,会使线程等待,直到队列中有数据或者队列有空间的时候,线程会被唤醒。

ArrayBlockingQueuq:没有空间时,put()存放数据会阻塞,take()取数据时,队列为空,会阻塞。
new ArrayBlockQueue(10,true);可以使用公平锁和非公平锁。true为公平锁,false为非公平锁。

LinkedBlockingQueue:和ArrayBlockingQueue大体上一样,但是ArrayBlockingQueue要比LinkedBlockingQueue效率高。LinkedBlockingQueue支持队列头部的操作。

SynchronousQueue:是一种阻塞队列,没有容量。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值