浅谈Java线程池

public class ThreadPoolExecutor extends AbstractExecutorService {
    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
            long keepAliveTime, TimeUnit unit,
            BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
            RejectedExecutionHandler handler);
}

corePoolSize :核心池的大小,默认情况下,在创建了线程池后,线程池中的线程数为 0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到 corePoolSize 后,就会把到达的任务放到缓存队列当中;prestartAllCoreThreads()或者 prestartCoreThread()方法,预在线程池中创建 corePoolSize 个或者 1 个线程。
maximumPoolSize :线程池最大线程数,它表示在线程池中最多能创建多少个线程;
keepAliveTime:表示线程没有任务执行时最多保持多长时间会终止。默认情况下,只有当线程池中的线程数大于 corePoolSize时,keepAliveTime 才会起作用,直到线程池中的线程数不大于 corePoolSize,但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于 corePoolSize 时,keepAliveTime 参数也会起作用,直到线程池中的线程数为 0;
unit :参数 keepAliveTime 的时间单位,有 7 种取值,在 TimeUnit 类中有 7 种静态属性DAYS/HOURS/MINUTES/SECONDS/MILLISECONDS/MICROSECONDS /NANOSECONDS;
workQueue:一个阻塞队列
  1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
  2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为 Integer.MAX_VALUE;
  3)SynchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是直接新建一个线程来执行新来的任务。
  4)PriorityBlockingQueue:一个具有优先级的无限阻塞队列
threadFactory :线程工厂,主要用来创建线程
handler :表示当拒绝处理任务时的策略,有以下四种取值:
  ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出 RejectedExecutionException 异常。
  ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
  ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
  ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

这里写图片描述
ThreadPoolExecutor extends abstract class AbstractExecutorService implements ExecutorService extends Executor
execute()方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行;submit()方法是用来向线程池提交任务的,但是它和 execute()方法不同,它能够返回任务执行的结果,去看 submit()方法的实现,会发现它实际上还是调用的 execute()方法,只不过它利用了Future来获取任务执行结果。
shutdown()和 shutdownNow()是用来关闭线程池的。

1) 线程池状态
volatile int runState; //当前线程池的状态,它是一个 volatile 变量用来保证线程之间的可见性
static final int RUNNING = 0;//运行状态
static final int SHUTDOWN = 1;//线程池不能够接受新的任务,它会等待所有任务执行完毕;
static final int STOP = 2;//线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
static final int TERMINATED = 3;//当线程池处于 SHUTDOWN 或 STOP 状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为 TERMINATED 状态

2) 任务的执行
线程池的主要处理流程:
这里写图片描述
1、如果当前线程池中的线程数目小于 corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;
2、如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;
3、若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
4、如果当前线程池中的线程数目达到 maximumPoolSize,则会采取任务拒绝策略进行处理;如果线程池中的线程数量大于 corePoolSize 时,如果某线程空闲时间超过 keepAliveTime,线程将被终止,直至线程池中的线程数目不大于 corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过 keepAliveTime,线程也会被终止。

在Java中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池:
Executors.newSingleThreadExecutor(); //创建容量为 1 的缓冲池
Executors.newFixedThreadPool(int); //创建固定容量大小的缓冲池
Executors.newCachedThreadPool(); //创建一个缓冲池,缓冲池容量大小为 Integer.MAX_VALUE
newSingleThreadExecutor 将 corePoolSize 和 maximumPoolSize 都设置为 1,使用 LinkedBlockingQueue;
newFixedThreadPool 创建的线程池 corePoolSize 和 maximumPoolSize 值是相等的,它使用 LinkedBlockingQueue;
newCachedThreadPool 将 corePoolSize 设置为 0,将 maximumPoolSize 设置为 Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来
了任务就创建线程运行,当线程空闲超过 60 秒,就销毁线程。

一般需要根据任务的类型来配置线程池大小:
如果是 CPU 密集型任务,就需要尽量压榨 CPU,参考值可以设为 N(CPU)+1;如果是 IO 密集型任务,参考值可以设置为 2*N(CPU)

3) 线程同步通信
Java 线程的两个特性,可见性和有序性
synchronized : 1. 获得同步锁;2. 清空工作内存;3. 从主内存拷贝对象副本到工作内存; 4. 执行代码(计算或者输出等);5. 刷新主内 存数据;6. 释放同步锁。
使用 synchronized 修饰的方法或者代码块可以看成是一个原子操作。既保证了多线程的并发有序性,又保证了多线程的内存可见性。每个锁(monitor)都有两个队列,一个是就绪队列(等待获取锁),一个是阻塞队列(wait,notify)
volatile 是第二种 Java 多线程同步的机制,变量可以被 volatile 修饰,在这种情况下内存模型(主内存和线程工作内存)确保所有线程可以看到一致的变量值。volatile 可以保证内存可见性,不能保证并发有序性。volatile 和 final 不能同时修饰一个字段。
Lock 是 java.util.concurrent.locks 包下的接口,Lock 实现提供了比使用 synchronized 方法和语句,可获得更广泛的锁操作,它能以更优雅的方式处理线程同步问题。用 sychronized 修饰的方法或者语句块在代码执行完之后锁自动释放,而用 Lock 需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在 try 内,释放锁放在 finally内。

private Lock lock = new ReentrantLock();// 锁对象
lock.lock();
try { 
    //同步语句块
    } 
finally { 
    lock.unlock();// 释放锁
    }

private ReadWriteLock rwl = new ReentrantReadWriteLock();
rwl.writeLock().lock/unlock();
rwl.readLock().lock/unlock();

wait notify
在调用 wait 方法时,都是用 while 判断条件的,而不是 if,在 wait 方法说明中,也推荐使用 while,因为在某些特定的情况下,线程有可能被假唤醒,使用 while 会循环检测更稳妥。wait 和 notify 方法必须工作于 synchronized 内部,且这两个方法只能由锁对象来调用。
Condition
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意Lock 实现组合使用,为每个对象提供多个等待 set (wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。在 Condition 中,用 await()替换 wait(),用 signal()替换 notify(),用 signalAll()替换 notifyAll(),传统线程的通信方式,Condition 都可以实现,这里注意,Condition 是被绑定到 Lock 上的,要创建一个 Lock 的 Condition 必须用lock.newCondition()方法。
BlockingQueue
BlockingQueue 最终会有四种状况,抛出异常、返回特殊值、阻塞、超时,下表总结了这些方法:
这里写图片描述
BlockingQueue 是个接口,有如下实现类:
1、ArrayBlockQueue:一个由数组组成的有界阻塞队列。此队列按先进先出原则对元素进行排序。创建其对象必须明确大小,像数组一样。
2、LinkedBlockQueue:一个可改变大小的阻塞队列。此队列按先进先出原则对元素进行排序。创建其对象如果没有明确大小,默认值是Integer.MAX_VALUE。链接队列的吞吐量通常要高于基于数组的队列,但是在大多数并发应用程序中,其可预知的性能要低。
3、PriorityBlockingQueue:类似于LinkedBlockingQueue,但其所含对象的排序不是 先进先出,而是依据对象的自然排序顺序或者是构造函数所带的 Comparator 决定的顺序。
4、SynchronousQueue:同步队列。同步队列没有任何容量,每个插入必须等待另一个线程移除,反之亦然。

Java 线程:线程中断、线程让步、线程睡眠、线程合并
void interrupt();//并不是中断线程的执行,而是为调用该方法的线程对象打上一个标记,设置其中断状态为 true,通过isInterrupted()方法可以得到这个线程状态,
如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long,int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException。我们可以捕获该异常,并且做一些处理。另外Thread.interrupted()方法是一个静态方法,它是判断当前线程的中断状态,需要注意的是,线程的中断状态会由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。

static void yield();//线程让步用于正在执行的线程,在某些情况下让出 CPU 资源,让给其它线程执行,如果存在 synchronized线程同步的话,线程让步不会释放锁(监视器对象)

static void sleep(long millis)/sleep(long millis, int nanos)//线程睡眠的过程中,如果是在 synchronized 线程同步内,是持有锁(监视器对象)的。

void join()/join(long millis)/join(long millis, int nanos)// 线程合并是优先执行调用该方法的线程,再执行当前线程。

Timer 和 TimerTask :TimerTask 是一个抽象类,实现了 Runnable 接口; 一个 Timer 可以调度任意多个 TimerTask,它会将 TimerTask 存储在一个队列中,顺序调度,如果想两个 TimerTask 并发执行,则需要创建两个 Timer。
timer.schedule(new TimerTask(), 2000);

线程池
1、创建一个可重用固定线程集合的线程池,以共享的无界队列方式来运行这些线程。

ExecutorService threadPool = Executors.newFixedThreadPool(3);// 创建可以容纳 3 个线程的线程池

2 创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。

ExecutorService threadPool = Executors.newCachedThreadPool();// 线程池的大小会根据执行的任务数动态分配

3 创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。

ExecutorService threadPool = Executors.newSingleThreadExecutor();// 创建单个线程的线程池,如果当前线程在执行任务时突然中断,则会创建一个新的线程替代它继续执行任务

4、创建一个可安排在给定延迟后运行命令或者定期地执行的线程池。

ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(3);// 效果类似于 Timer 定时器
schedulePool.schedule();//执行一次 schedulePool.scheduleAtFixedRate()//循环执行

Callable Future

1.Callable+Future
    ExecutorService executor = Executors.newCachedThreadPool();
    Callable task = new Task();
    Future<Integer> result = executor.submit(task);
    result.get();//阻塞
2.Callable+FutureTask
    ExecutorService executor = Executors.newCachedThreadPool();
    Task task = new Task();
    FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
    executor.submit(futureTask);//Thread thread = new Thread(futureTask);

线程本地变量 ThreadLocal

Fork/Join-Java 并行计算框架
并行是指系统内有多个任务同时执行,而并发是指系统内有多个任务同时存在
这里写图片描述

猜你喜欢

转载自blog.csdn.net/weixin_37672169/article/details/80229639