线程池与ThreadLocal解析

276 阅读4分钟

声明:本文是自己自学慕课网悟空老师的《玩转Java并发工具,精通JUC,成为并发多面手》的线程池和ThreadLocal部分后整理而成课程笔记。

如有侵权,请私信我并第一时间删除本文。

image-20220213221353131

image-20220213221408498

并发工具类一分类 1.为了并发安全:互斥同步、非互斥同步、无同步方案 2.管理线程、提高效率

3.线程协作

image-20220213221957084

image-20220213222026999

image-20220213223223012

image-20220213223314401

image-20220213223402555

image-20220213223509130

image-20220213223557497

image-20220213223641846

image-20220213223727117

image-20220213223823568

image-20220213223855498

image-20220213223912290

image-20220213223934037

image-20220213224101816

image-20220213224116002

image-20220213224151134

image-20220213224211391

1. 线程池

线程池一治理线程的法宝

1.1 线程池的自我介绍

◆线程池的重要性 ◆什么是“池” 软件中的“池”,可以理解为计划经济

◆如果不使用线程池,每个任务都新开一个线程处理 ◆只有一个任务,只需开一个线程 ◆多个任务用,for循环每个任务创建一个线程

image-20220213230529216

image-20220213230610912

image-20220213230657324

image-20220213230925613

1.2 创建和停止线程池

image-20220213231024305

image-20220213231055659

image-20220213231505008

image-20220214094149729

image-20220214094325196

image-20220214094425058

image-20220214094936770

image-20220214095023055

image-20220214095200408

image-20220214095651563

image-20220214101101791

image-20220214101234374

image-20220214101305932

image-20220214102927547

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 描述:     演示newFixedThreadPool
 */
public class FixedThreadPoolTest {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Task());
        }
    }
}

class Task implements Runnable {


    @Override
    public void run() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName());
    }
}

image-20220214102357961

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 描述:     演示newFixedThreadPool出错的情况
 */
public class FixedThreadPoolOOM {

    private static ExecutorService executorService = Executors.newFixedThreadPool(1);
    public static void main(String[] args) {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            executorService.execute(new SubThread());
        }
    }

}

class SubThread implements Runnable {


    @Override
    public void run() {
        try {
            Thread.sleep(1000000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

image-20220214102543132

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 描述:演示SingleThreadExecutor    
 */
public class SingleThreadExecutor {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Task());
        }
    }
}

image-20220214103033807

image-20220214103103167

image-20220214103158097

image-20220214103619187

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 描述:     TODO
 */
public class CachedThreadPool {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Task());
        }
    }
}

image-20220214103800389

image-20220214103927278

package threadpool;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 描述:     TODO
 */
public class ScheduledThreadPoolTest {

    public static void main(String[] args) {
        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(10);
        //延迟5秒后运行
//        threadPool.schedule(new Task(), 5, TimeUnit.SECONDS);
        //初始1秒,每隔3秒运行。
        threadPool.scheduleAtFixedRate(new Task(), 1, 3, TimeUnit.SECONDS);
    }
}

image-20220214104357369

image-20220214104529200

停止线程池的正确方法

  1. shutdown

  2. isShutdown

  3. isTerminated

  4. awaitTermination

  5. shutdownNow

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * 描述:     演示关闭线程池
 */
public class ShutDown {

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100; i++) {
            executorService.execute(new ShutDownTask());
        }
        Thread.sleep(1500);
        
        //List<Runnable> runnableList是已经放到工作队列中,还没有执行的线程,需要返回用变量接收
//        List<Runnable> runnableList = executorService.shutdownNow();

        executorService.shutdown();
        executorService.execute(new ShutDownTask());
        
//        boolean b = executorService.awaitTermination(7L, TimeUnit.SECONDS);  //等待7秒,这7秒内钟,线程会阻塞,不会继续往下运行,7秒之后检查线程池是否终止,如果7秒内线程终止true,否则false
//        System.out.println(b);
//        System.out.println(executorService.isShutdown()); //此时还没有shutdown,所以是false
//        executorService.shutdown();
//        System.out.println(executorService.isShutdown()); //true
//        System.out.println(executorService.isTerminated()); //此时已经shutdown了,但是线程池还没有终止,所以是false
//        Thread.sleep(10000); //等待1秒
//        System.out.println(executorService.isTerminated()); //线程池已经终止,所以是true

//        executorService.execute(new ShutDownTask());
    }
}

class ShutDownTask implements Runnable {


    @Override
    public void run() {
        try {
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + "被中断了");
        }
    }
}

1.3. 常见线程池的特点和用法

◆FixedThreadPool

image-20220214105340230

◆CachedThreadPool

image-20220214105356843

◆ScheduledThreadPool

◆SingleThreadExecutor

image-20220214105431078

image-20220214105643750

image-20220214105848394

image-20220214110000854

1.4 任务太多, 怎么拒绝?

◆拒绝时机

1.当Executor关闭时,提交新任务会被拒绝。

2.以及当Executor对最大线程和工作队列容量使用有限边界并 且已经饱和时

image-20220214140339228

4种拒绝策略

AbortPolicy 报异常

DiscardPolicy 默认丢弃任务

DiscardOldestPolicy 丢弃最老的任务

CallerRunsPolicy 谁提交任务谁执行(主线程给线程池提交任务,会让主线程执行任务,避免了任务损失,让提交任务速度降低下来,让线程池有缓冲时间)

1.5 钩子方法,给线程池加点料

◆每个任务执行前后

◆日志、统计

◆代码演示

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 描述:     演示每个任务执行前后放钩子函数
 */
public class PauseableThreadPool extends ThreadPoolExecutor {

    private final ReentrantLock lock = new ReentrantLock();
    private Condition unpaused = lock.newCondition();
    private boolean isPaused; //标志位


    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime,
            TimeUnit unit,
            BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime,
            TimeUnit unit, BlockingQueue<Runnable> workQueue,
            ThreadFactory threadFactory) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
    }

    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime,
            TimeUnit unit, BlockingQueue<Runnable> workQueue,
            RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
    }

    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime,
            TimeUnit unit, BlockingQueue<Runnable> workQueue,
            ThreadFactory threadFactory, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory,
                handler);
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        lock.lock();
        try {
            while (isPaused) {
                unpaused.await();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    //暂停
    private void pause() {
        lock.lock();
        try {
            isPaused = true;
        } finally {
            lock.unlock();
        }
    }

    //恢复
    public void resume() {
        lock.lock();
        try {
            isPaused = false;
            unpaused.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        PauseableThreadPool pauseableThreadPool = new PauseableThreadPool(10, 20, 10l,
                TimeUnit.SECONDS, new LinkedBlockingQueue<>());
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("我被执行");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        for (int i = 0; i < 10000; i++) {
            pauseableThreadPool.execute(runnable);
        }
        Thread.sleep(1500);
        pauseableThreadPool.pause();
        System.out.println("线程池被暂停了");
        Thread.sleep(1500);
        pauseableThreadPool.resume();
        System.out.println("线程池被恢复了");

    }
}

1.6 实现原理、源码分析

线程池组成部分

image-20220214143912936

  • 线程池管理器

  • 工作线程

  • 任务列队

  • 任务接口( Task )

    image-20220214144024133

image-20220214144039411

  • Executor(interface)

    只有一个image-20220214144310225

  • ExecutorService(继承了Executor接口)

    在Executor类上增加了部分方法,比如shutDown方法

  • Executors(是一个工具类,帮助快速创建线程池)

    image-20220214144624784

image-20220214144940679

image-20220214154407481

image-20220214154429493

ThreadPoolExecutor类下的execute原理解析

image-20220216162235695

image-20220214154233400

调用task.run().

1.7 使用线程池的注意点

image-20220214154751755

2. ThreadLocal

image-20220215234315491

2.1 两大使用场景一ThreadLocal的用途

2.1.1 典型场景1 :每个线程需要一个独享的对象

(通常是工具类,典型需要使用的类有SimpleDateFormat和Random )

  • 每个Thread内有自己的实例副本,不共享
  • 比喻:教材只有一本,一起做笔记有线程安全问题。复印后没问题
  • SimpleDateFormat的进化之路

image-20220214175720740

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 描述:     两个线程打印日期
 */
public class ThreadLocalNormalUsage00 {

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                String date = new ThreadLocalNormalUsage00().date(10);
                System.out.println(date);
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                String date = new ThreadLocalNormalUsage00().date(104707);
                System.out.println(date);
            }
        }).start();
    }

    public String date(int seconds) {
        //参数的单位是毫秒,从1970.1.1 00:00:00 GMT计时
        Date date = new Date(1000 * seconds);
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return dateFormat.format(date);
    }
}

image-20220214180951969

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 描述:     1000个打印日期的任务,用线程池来执行
 */
public class ThreadLocalNormalUsage03 {

    public static ExecutorService threadPool = Executors.newFixedThreadPool(10);
    static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    String date = new ThreadLocalNormalUsage03().date(finalI);
                    System.out.println(date);
                }
            });
        }
        threadPool.shutdown();
    }

    public String date(int seconds) {
        //参数的单位是毫秒,从1970.1.1 00:00:00 GMT计时
        Date date = new Date(1000 * seconds);
        return dateFormat.format(date);
    }
}

发生线程不安全情况image-20220214203056463

线程不安全原因:image-20220214203032848

image-20220214220845858

image-20220214223136154

	//加锁方法
	public String date(int seconds) {
        //参数的单位是毫秒,从1970.1.1 00:00:00 GMT计时
        Date date = new Date(1000 * seconds);
        String s = null;
        synchronized (ThreadLocalNormalUsage04.class) {
            s = dateFormat.format(date);
        }
        return s;
    }

image-20220216000635117

ThreadLocal写法

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 描述:     利用ThreadLocal,给每个线程分配自己的dateFormat对象,保证了线程安全,高效利用内存
 */
public class ThreadLocalNormalUsage05 {

    public static ExecutorService threadPool = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    String date = new ThreadLocalNormalUsage05().date(finalI);
                    System.out.println(date);
                }
            });
        }
        threadPool.shutdown();
    }

    public String date(int seconds) {
        //参数的单位是毫秒,从1970.1.1 00:00:00 GMT计时
        Date date = new Date(1000 * seconds);
//        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal2.get();
        return dateFormat.format(date);
    }
}

class ThreadSafeFormatter {

    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };

    //lambda表达式写法,同dateFormatThreadLocal等价
    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal2 = ThreadLocal
            .withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}

2.1.2 典型场景2 :每个线程内需要保存全局变量

(例如在拦截器中获取用户信息) ,可以让不同方法直接使用,避免参数传递的麻烦

image-20220214223529270

image-20220214230026173

image-20220214230049374

image-20220216001226109

image-20220216001314734

image-20220216001331809

image-20220214230116563

/**
 * 描述:     演示ThreadLocal用法2:避免传递参数的麻烦
 */
public class ThreadLocalNormalUsage06 {

    public static void main(String[] args) {
        new Service1().process("小冰");

    }
}

class Service1 {

    public void process(String name) {
        User user = new User(name);
        UserContextHolder.holder.set(user);
        new Service2().process();
    }
}

class Service2 {

    public void process() {
        User user = UserContextHolder.holder.get();
        ThreadSafeFormatter.dateFormatThreadLocal.get();
        System.out.println("Service2拿到用户名:" + user.name);
        new Service3().process();
    }
}

class Service3 {

    public void process() {
        User user = UserContextHolder.holder.get();
        System.out.println("Service3拿到用户名:" + user.name);
        UserContextHolder.holder.remove();
    }
}

class UserContextHolder {

    public static ThreadLocal<User> holder = new ThreadLocal<>();


}

class User {

    String name;

    public User(String name) {
        this.name = name;
    }
}

运行结果

image-20220214232151495

总结

ThreadLocal的两个作用

1.让某个需要用到的对象在线程间隔离( 每 个线程都有自己的独立的对象)

2.在任何方法中都可以轻松获取到该对象

根据共享对象的生成时机不同,选择initialValue或set来保存对象

image-20220214234528106

image-20220214234606910

2.2 ThreadLocal的好处

image-20220214235929372

image-20220215131156444

2.3 原理、 源码分析

image-20220215132321999

Thread类中有一个ThreadLocalMap的成员变量,一个ThreadLocalMap对象中可以存储很多ThreadLocal对象

image-20220215132356866

2.3.1 T initialValue() :初始化

1.该方法会返回当前线程对应的"初始值”,这是一个延迟加载的方法,只有在调用get的时候,才会触发。

initialValue方法:是没有默认实现的,如果我们要用initialValue方法,需要自己实现,通常是匿名内部类的方式(回顾代码)。

ThreadLocal类下:

image-20220215133517865

image-20220215133541634

image-20220215133717975

2.当线程第一次使用get方法访问变量时,将调用此方法,除非 线程先前调用了set方法,在这种情况下,不会为线程调用本 initialValue方法

1和2这正对应了ThreadLocal的两种典型用法

3.通常,每个线程最多调用一次initialValue( )方法,但如果已经调用了 remove()后,再调用get() ,则可以再次调用此方法初始化。

4.如果不重写本方法,这个方法会返回null。一般使用匿名内部 类的方法来重写initialValue()方法,以便在后续使用中可以初 始化副本对象。

2.3.2 void set(T t)

为这个线程设置一个新值,和setInitialValue方法很类似。

image-20220215225221269

2.3.3 T get() :

得到这个线程对应的value。如果是首次调用get() ,则会调用initialize来得到这个值

image-20220215135418988

image-20220215135434869

image-20220215225314572

2.3.4 void remove()

删除对应这个线程的值

image-20220215225456109

image-20220215230759184

image-20220216005158854

image-20220216005231231

image-20220215231055199

2.4.注意点

2.4.1 threadLocal引起的内存泄漏

什么是内存泄漏:某个对象不再有用,但是占用的内存却不能被回收

image-20220216003729660

  • 弱引用的特点是,如果这个对象只被弱引用关联(没有任何强引用关联) ,那么这个对象就可以被回收

  • ThreadLocalMap的每个Entry都是一个对key的弱引用,同时 每个Entry都包含了一个对value的强引用

  • 正常情况下,当线程终止,保存在ThreadLocal里的value会被垃 圾回收,因为没有任何强引用了

image-20220215231254551

image-20220215231326363

image-20220215231354957

image-20220215231522293

image-20220215231538256

image-20220215231753778

image-20220215231852934

2.4.2 threadLocal引起的空指针异常

/**
 * 描述:     TODO
 */
public class ThreadLocalNPE {

    ThreadLocal<Long> longThreadLocal = new ThreadLocal<Long>();

    public void set() {
        longThreadLocal.set(Thread.currentThread().getId());
    }

    public long get() {
        return longThreadLocal.get();
    }

    public static void main(String[] args) {
        ThreadLocalNPE threadLocalNPE = new ThreadLocalNPE();
        System.out.println(threadLocalNPE.get());
//        Thread thread1 = new Thread(new Runnable() {
//            @Override
//            public void run() {
//                threadLocalNPE.set();
//                System.out.println(threadLocalNPE.get());
//            }
//        });
//        thread1.start();
    }
}

image-20220215232726385

**原因:**threadLocalNPE调用set方法就先get,get会调用initialValue方法,默认null,由于threadlocal是包装类,get返回的是long,会进行装箱拆箱,null转型long,导致空指针异常,将get返回的long改成Long就可以解决问题了

image-20220215233152915

image-20220215233043838

2.4.3 共享对象

image-20220215233722047

image-20220215234029078

2.5.实际应用场景--在Spring中的实例分析

image-20220215234058262

image-20220215234109397

image-20220215234125851

image-20220215234216878

2.6.常见面试题