声明:本文是自己自学慕课网悟空老师的《玩转Java并发工具,精通JUC,成为并发多面手》的线程池和ThreadLocal部分后整理而成课程笔记。
如有侵权,请私信我并第一时间删除本文。
并发工具类一分类 1.为了并发安全:互斥同步、非互斥同步、无同步方案 2.管理线程、提高效率
3.线程协作
1. 线程池
线程池一治理线程的法宝
1.1 线程池的自我介绍
◆线程池的重要性 ◆什么是“池” 软件中的“池”,可以理解为计划经济
◆如果不使用线程池,每个任务都新开一个线程处理 ◆只有一个任务,只需开一个线程 ◆多个任务用,for循环每个任务创建一个线程
1.2 创建和停止线程池
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());
}
}
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();
}
}
}
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());
}
}
}
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());
}
}
}
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);
}
}
停止线程池的正确方法
-
shutdown
-
isShutdown
-
isTerminated
-
awaitTermination
-
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
◆CachedThreadPool

◆ScheduledThreadPool
◆SingleThreadExecutor
1.4 任务太多, 怎么拒绝?
◆拒绝时机
1.当Executor关闭时,提交新任务会被拒绝。
2.以及当Executor对最大线程和工作队列容量使用有限边界并 且已经饱和时
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 实现原理、源码分析
线程池组成部分
-
线程池管理器
-
工作线程
-
任务列队
-
任务接口( Task )
-
Executor(interface)
只有一个
-
ExecutorService(继承了Executor接口)
在Executor类上增加了部分方法,比如shutDown方法
-
Executors(是一个工具类,帮助快速创建线程池)
ThreadPoolExecutor类下的execute原理解析
调用task.run().
1.7 使用线程池的注意点
2. ThreadLocal
2.1 两大使用场景一ThreadLocal的用途
2.1.1 典型场景1 :每个线程需要一个独享的对象
(通常是工具类,典型需要使用的类有SimpleDateFormat和Random )
- 每个Thread内有自己的实例副本,不共享
- 比喻:教材只有一本,一起做笔记有线程安全问题。复印后没问题
- SimpleDateFormat的进化之路
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);
}
}
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);
}
}
发生线程不安全情况
线程不安全原因:
//加锁方法
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;
}
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 :每个线程内需要保存全局变量
(例如在拦截器中获取用户信息) ,可以让不同方法直接使用,避免参数传递的麻烦
/**
* 描述: 演示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;
}
}
运行结果
总结
ThreadLocal的两个作用
1.让某个需要用到的对象在线程间隔离( 每 个线程都有自己的独立的对象)
2.在任何方法中都可以轻松获取到该对象
根据共享对象的生成时机不同,选择initialValue或set来保存对象
2.2 ThreadLocal的好处
2.3 原理、 源码分析
Thread类中有一个ThreadLocalMap的成员变量,一个ThreadLocalMap对象中可以存储很多ThreadLocal对象
2.3.1 T initialValue() :初始化
1.该方法会返回当前线程对应的"初始值”,这是一个延迟加载的方法,只有在调用get的时候,才会触发。
initialValue方法:是没有默认实现的,如果我们要用initialValue方法,需要自己实现,通常是匿名内部类的方式(回顾代码)。
ThreadLocal类下:
2.当线程第一次使用get方法访问变量时,将调用此方法,除非 线程先前调用了set方法,在这种情况下,不会为线程调用本 initialValue方法
1和2这正对应了ThreadLocal的两种典型用法
3.通常,每个线程最多调用一次initialValue( )方法,但如果已经调用了 remove()后,再调用get() ,则可以再次调用此方法初始化。
4.如果不重写本方法,这个方法会返回null。一般使用匿名内部 类的方法来重写initialValue()方法,以便在后续使用中可以初 始化副本对象。
2.3.2 void set(T t)
为这个线程设置一个新值,和setInitialValue方法很类似。
2.3.3 T get() :
得到这个线程对应的value。如果是首次调用get() ,则会调用initialize来得到这个值
2.3.4 void remove()
删除对应这个线程的值
2.4.注意点
2.4.1 threadLocal引起的内存泄漏
什么是内存泄漏:某个对象不再有用,但是占用的内存却不能被回收
-
弱引用的特点是,如果这个对象只被弱引用关联(没有任何强引用关联) ,那么这个对象就可以被回收
-
ThreadLocalMap的每个Entry都是一个对key的弱引用,同时 每个Entry都包含了一个对value的强引用
-
正常情况下,当线程终止,保存在ThreadLocal里的value会被垃 圾回收,因为没有任何强引用了

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