线程池详解

实现多线程的集中方式

一、继承Thread类

继承Thread类,重写run方法, start启动线程

二、实现Runnable接口

实现Runnable接口,重写run方法,通过Thread的start方法启动线程

三、使用内部类方式

并不是一种新的实现方式,只是另外一种写法,通过匿名内部类方式来实现,方式依赖有继承Thread和实现Runnable两种写法

四、定时器

就是基于线程的一个工具类

五、带返回值的线程实现方式

1.创建一个类实现Callable接口,实现call方法

2.创建一个FutureTask指定Callable对象,作为线程任务

3.创建线程指定线程任务

4.启动线程

六、基于线程池创建线程

谈论线程池之前先说下什么是This逃逸?

对象在还没没有构造完成时,This引用已经发布出去。这种一般会有两种情况:

一、在构造器中启动线程:启动的线程的任务是内部类,在内部中xxx.this访问了外部类的实例,就会发生访问到还未初始化完成的变量

解决方法:不要在构造器中启动线程,尝试在外部启动

二、在构造器中注册事件,这是因为在构造器中监听事件是有回调函数。(可能访问了操作了的实例变量)

解决方法:将事件监听事件放置于构造器外。

总结:this逃逸一般多发生在多线程中,引起this逃逸的问题是在多线程滥用引用。

那么线程中的Executor框架就是有助于避免this逃逸问题。

Execuor框架不仅包括了线程池的管理,还提供了线程工厂、队列以及拒绝策略等。

线程池提供了一种限制和管理资源。

使用线程池的好处:

一、降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗

二、提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立刻执行

三、提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统稳定性,使用线程池可以进行统一得分配,调优和监控。

四种常用线程池:

FixedThreadPool,CachedThreadPool,SingleThreadPool,ScheduledThreadPool

FixedThreadPool:

keepAliveTime:0,不存在空闲线程

BlockingQueue:LinkedBlockingQueue(Integer.MAXValue)无界队列,如果瞬时任务数目非常大,会有OOM风险

有限线程数的线程池,它的核心线程数和最大线程数是一致的,线程数目是固定的。当运行线程数达到最大线程数时,如果此时再有任务提交,任务将放入无界阻塞队列中。等到有线程空闲时再从队列中取出任务继续执行。

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

2.由于使用了无界队列,所以maximumpoolsize将是一个无效参数,因为不存在队列满的情况

3.由于maximumpoolsize=corePoolsize和无界队列存在keepAliveTime也是一个无效参数

4.在任务比较多的情况会出现OOM内存溢出

CachedThreadPool:

corePoolSize:0

maximumPoolSize:Integer.MaxValue.如果达到上限,没有任何线程能够处理,肯定会跑出OOM异常

BlockingQueue:SynchronousQueue。一种不存在任何元素的阻塞队列

无限线程数的线程池,线程数会根据需要自动增加或减少,如果有任务需要执行,且目前没有空闲的线程可用,就会创建一个新的线程执行任务,当线程空闲时间超过60s时,就会被回收。

如果主线程提交任务速度高于maxnimumpool时,会不断创建新的线程,极端情况下会导致耗尽cpu和内存资源

大量创建线程会导致OOM

SingleThreadPool:

corePoolSize:1,maximumPoolSize:1

keepAliveTime:0

BlockingQueue是LinkedBlockingQueue(Integer.MaxValue)。这是无界队列,如果瞬间任务数非常大,会有OOM的风险。

只要一个线程的线程池,特点是保证所有任务都在同一个线程中顺序执行,避免多线程情况下的竞争和死锁问题,适用于按顺序执行多个任务的场景

ScheduledThreadPool:

定时任务线程池,可以在指定的时间执行任务,也可以定期执行任务

corePoolSize是自定义

maximumPoolSize:Integer.maxvalue。也会存在OOM风险

BlockQueue:DelayedWorkQueue

工作队列:

ArrayBlockingQueue:

ArrayBlockingQueue(有界队列)是一个用数组实现的有界阻塞队列,按FIFO排序量,尾部插入,头部获取
初始化时需指定队列的容量 capacity
类比到上面的场景,就是椅子的数量为初始容量capacity


LinkedBlockingQueue:


链表阻塞队列,这是一个无界队列,遵循FIFO,尾部插入,头部获取
初始化时可不指定容量,此时默认的容量为Integer.MAX_VALUE,基本上相当于无界了,此时队列可一直插入(如果处理任务的速度小于插入的速度,时间长了就有可能导致OOM)
类比到上面的场景,就是椅子的数量为Integer.MAX_VALUE


SynchronousQueue:


同步队列,阻塞队列的特殊版,即没有容量的阻塞队列,随进随出,不做停留
类比到上面的场景,就是椅子的数量为0,来一个人就去柜台办理,如果柜台满了,就拒绝


PriorityBlockingQueue


优先级阻塞队列,这是一个无界队列,不遵循FIFO,而是根据任务自身的优先级顺序来执行
初始化可不指定容量,默认11(既然有容量,怎么还是无界的呢?因为它添加元素时会进行扩容)
类比到上面的场景,就是新来的可以插队办理业务,好比各种会员

DelayQueue

DelayQueue(延迟队列)是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool线程池使用了这个队列。

线程工厂

每当线程池需要创建一个线程时,都是通过线程工厂方法来完成的。在ThreadFactory中只定义了一个方法newThread,每当线程池需要创建一个新线程时都会调用这个方法。Executors提供的线程工厂有两种,一般使用默认的,当然如果有特殊需求,也可以自己定制。

  1. DefaultThreadFactory:默认线程工厂,创建一个新的、非守护的线程,并且不包含特殊的配置信息。
  2. PrivilegedThreadFactory:通过这种方式创建出来的线程,将与创建privilegedThreadFactory的线程拥有相同的访问权限、 AccessControlContext、ContextClassLoader。如果不使用privilegedThreadFactory, 线程池创建的线程将从在需要新线程时调用execute或submit的客户程序中继承访问权限。
  3. 自定义线程工厂:可以自己实现ThreadFactory接口来定制自己的线程工厂方法。

Execuor框架结构

一、任务(Runnable、Callable)

执行任务需要实现Runnable接口或者Callable接口。Runnable接口或Callable接口实现类都可以被ThreadPoolExecutor或ScheduledThreadPoolExecuor执行。

二、任务的执行(Executor)

Execuor接口,ExecutorService接口是集成了Execuor接口.ThreadPoolExecuor和ScheduledThreadExecutor这两个关键类是实现了ExecuorService接口.

三、异步计算的结果(Future)

Future接口以及Future接口的实现类FutureTask类都可以代表异步计算的结果

1.主线程首先要创建实现Runnable/callable接口的任务对象

2.将任务对象交给ExecuteService执行,ExecuteService有两个方法可以接受任务对象,分别时submite()和execute()。execute()方法执行任务对象没有返回值,submit()方法执行任务对象会返回FutureTask对象

3.如果执行了ExecutorService.submit(),ExecutorService对象将返回一个实现Future接口的对象。由于FutureTask也实现Runnable接口,因此也可以直接创建FututeTask类的任务,然后直接交给ExecutorService执行。

4.最后主线程可以执行FutureTask.get()方法来等待任务执行完成,主线程也可以执行FutureTask.cancel()方法来取消此任务的执行

ThreadPoolExecutor类分析:

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.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

三个最重要线程池参数corPoolSize,MaximunSize,workqueue

coePoolSize:最小可以同时运行的线程数量

maximumPoolSize:当队列中存放的任务达到队列容量时,当前可以同时运行的线程数量变道最大线程数

workQueue:当新任务来的时候先判断当前运行的线程数是否达到核心线程数,如果达到,新任务就会被存放在队列中

keepAliveTime:当线程池中线程数大于corePoolSize时,如果没有新任务提交,核心线程外的线程不会立即销毁,而是会等待,知道等待时间超过keepAliveTime才会被销毁掉

handler:饱和策略,如果当前同时运行的线程数量达到最大线程数量,并且队列也已经被放满了任务时,就会实现饱和策略,饱和策略有抛出异常来拒绝新任务处理(默认),不处理新任务直接丢弃,丢弃最早未处理的任务请求,CallersRunsPolicy(提供伸缩队列)

阿里巴巴java开发手册并发处理明确指出线程资源必须通过线程池提供,不允许在应用中自行显示创建线程,同时强制线程池不允许使用Execuors去创建,而是通过ThreadPoolExecutor构造函数的方式创建,

Executors创建线程池的弊端如下:

在SingleThreadPool和FixedThreadPool中使用的队列都是无界阻塞队列,允许队列的最大长度为Integer.MAXVALUE,可能对接大量请求,造成OOM

在CachedThreadPool和ScheduledThreadPool允许创建的线程数量为Integer.MAX-value可能也会创建大量线程,从而导致OOM。

所以推荐使用ThreadPoolExecutor的构造方法去自定义创建线程池

private static final int CORE_POOL_SIZE = 5;
private static final int MAX_POOL_SIZE = 10;
private static final int QUEUE_CAPACITY = 100;
private static final long KEEP_ALIVE_TIME = 1L;

ThreadPoolExecutor executor = new ThreadPoolExector(
CORE_POOL_SIZE , MAX_POOL_SIZE ,KEEP_ALIVE_TIME ,TimeUnit.SECONDS, 
new ArrayBlockingQueue<>(Queue_CAPACITY),
new ThreadPoolExecutor.CallersRunsPolicy()

)

1.线程先会执行5个任务,然后5个任务中有任务被执行完的话,就去拿新任务执行

每次只可能存在5个任务同事执行,剩下的5个任务会被放到等待队列中去,如果5个任务中如果有任务被执行完,线程池就会那新的任务执行。

Executor.shutdown();关闭线程池,线程池的状态变为shutdown。线程池不再接受新任务,但是队列中的任务得执行完毕

executor.shutdownnow():关闭线程池,线程状态变为stop,线程池会终止当前正在运行的任务,并停止处理排队的任务并返回正在等待执行的list

isshutdown():调用shutdown方法后返回true

isTermintaed():调用shutdown()方法后,并且所以提交的任务完成后返回为true。

怎么设置线程数

CPU密集型:n(cpu核心数)+1

密集型任务:2N

需要对内存中大量数据进行排序属于cpu密集型,设计到网络读取,文件读取属于IO密集型

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值