并发编程3

发布于:2025-03-19 ⋅ 阅读:(17) ⋅ 点赞:(0)

线程池

池:容器,集合

每次连接数据库都要创建一个连接对象,用完之后进行销毁,频繁创建销毁占用一定的开销,这时就提出来了“池”的概念

        可以创建一定数量的连接对象放入到一个池中,有链接到来时,从池中获取一个连接对象使用,使用完成后不进行销毁,而放回到这个池中,减少创建销毁的开销。

可以通俗的理解为一个线程集合,

从jdk5开始就有了线程池的实现,有两个类

ThreadPoolExecutor

Executors

实现线程池

线程池的优点

避免多线程的频繁创建和销毁,提高系统的效率,降低了创建和销毁线程的资源消耗。

统一管理线程,现成的创建和销毁都交由线程池管理,提高了响应速度。

ThreadPoolExecutor 类

Java.uitl.concurrent.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.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:核心池的大小

        在创建了线程池后,默认情况下,在创建了线程池后,线程池中的线程数为 0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到 corePoolSize 后,就会把到达的任务放到缓存队列当中

maximumPoolSize:线程池最大线程数
         表示在线程池中最多能创建多少个线程

keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。

        只有当线程池中的线程数大于 corePoolSize 时,keepAliveTime 才会起作用,直到线程池中的线程数不大于 corePoolSize,即当线程池中的线程数大于 corePoolSize 时,如果一个线程空闲的时间达到 keepAliveTime,则会终止,直到线程池中的线程数不超过 corePoolSize。

 unit:参数 keepAliveTime 的时间单位,由七种取值

workQueue 一个阻塞队列,用来存储等待执行的任务

        ArrayBlockingQueue:是一个用数组实现的有界阻塞队列,创建时必须设置长度,,按 FIFO 排序量。

        LinkedBlockingQueue:基于链表结构的阻塞队列,按 FIFO 排序任务,容量可以选择进行设置,不设置是一个最大长度为 Integer.MAX_VALUE;

threadFactory:线程工厂,主要用来创建线程

handler :表示当拒绝处理任务时的策略

        默认有四种:

        AbortPolicy 策略:该策略会直接抛出异常,阻止系统正常工作。

        CallerRunsPolicy 策略:只要线程池未关闭,该策略在调用者线程中运行当前的任务(如果任务被拒绝了,则由提交任务的线程(例如:main)直接执行此任务)。

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

        DiscardPolicy 策略:该策略丢弃无法处理的任务,不予任何处理。

线程池的运行流程 

 1.如果线程池中存活的核心线程数小于线程数 corePoolSize 时,线程池会创建一个核心线程去处理提交的任务。

2.如果线程池核心线程数已满,即线程数已经等于 corePoolSize,一个新提交的任务,会被放进任务队列 workQueue 排队等待执行。

3. 当线程池里面存活的线程数已经等于 corePoolSize 了,并且任务队列workQueue 也满,判断线程数是否达到 maximumPoolSize,即最大线程数是否已满,如果没到达,创建一个非核心线程执行提交的任务。

4.如果当前的线程数达到了 maximumPoolSize,还有新的任务过来的话,直接采用拒绝策略处理。

 execute 与 submit 的区别

执行任务除了可以使用 execute 方法还可以使用 submit 方法。它们的主要区别是:execute 适用于不需要关注返回值的场景,submit 方法适用于需要关注返回值的场景。

线程池的使用

1.创建ThreadPoolExecutor对象

2.提交任务

        execute 与 submit

3.关闭线程池

        shutdownNow 和 shutdown

        shutdownNow:对正在执行的任务全部发出 interrupt(),停止执行,对还未开始执行的任务全部取消,并且返回还没开始的任务列表。

        shutdown:当我们调用 shutdown 后,线程池将不再接受新的任务,但也不会去强制终止已经提交或者正在执行中的任务。

 ThreadLocal

线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本

		//创建一个ThreadLocal对象,复制保用来为每个线程会存一份变量,实现线程封闭
		private  static ThreadLocal<Integer> localNum = new ThreadLocal<Integer>(){
			@Override
			protected Integer initialValue() {
				return 0;
			}
		};

实现原理

首先 ThreadLocal 是一个泛型类,保证可以接受任何类型的对象。

一个线程内可以存在多个 ThreadLocal 对象, ThreadLocal 内 部 维 护 了 一 个 Map ,ThreadLocal 实 现 了 一 个 叫做 ThreadLocalMap 的静态内部类。而我们使用的 get()、set() 方法其实都是由这个 ThreadLocalMap 类对应的 get()、set() 方法实现的。
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        map.set(this, value);
    } else {
        createMap(t, value);
    }
}
public T get() {
   Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}
static class ThreadLocalMap {
       
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    ............
    ............
}

ThreadLocalMap实现原理 

Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每个线程有一个自己的ThreadLocalMap。ThreadLocalMap有自己的独立实现,可以简单地将它的key视作ThreadLocal,value为代码中放入的值(实际上key并不是ThreadLocal本身,而是它的一个弱引用)。每个线程在往某个ThreadLocal里塞值的时候,都会往自己的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。

 

 最 终 的 变 量 是 放 在 了 当 前 线 程 的 ThreadLocalMap 中 , 并 不 是 存 在ThreadLocal 上,ThreadLocal 作为 key。

ThreadLocal 内存泄漏问题

由于ThreadLocal这个类被WeakReference关联,为弱引用,可能会在下一次垃圾回收时被回收,但是value却被Entry对象关联,Entry又被TheadLocalMap关联,TheadLocalMap又被Thead关联着,只要当前线程不销毁,value就不会销毁。ThreadLocal作为TheadLocalMap的键,当key被回收时,就无法获取value,但value不会销毁,就造成了内存泄漏。

每次使用完 ThreadLocal 都调用它的 remove()方法清除数据。

本文参考:ThreadLocal原理、ThreadLocal的使用、ThreadLocal内存泄漏、ThreadLocal的坑