ThreadLocal类的一个小应用
1. idea
先前使用多线程模拟体检科室体检,但是循环使用的是while(true),一直在思考加一个线程去判断是否完成体检,然后终止这些死循环,后来发现这种idea显然绕远了。现在借助ThreadLocal类进行计数,科室创建时接收一个参数——总检查人数,每次检查完一个人数就更新ThreadLocal里的value,直到检查完成,就可以打断循环了。
这种做法的好处是,每个线程都可以自己管理自己。不好之处就是线程的代码将会更加臃肿,因为判断是否结束的语句和更新ThreadLocal值的语句都要加到先前的死循环里面,加上别的代码估计得快百行了。希望后续有经验了可以优化一下。
2.关于ThreadLocal
2.1 ThreadLocal作用:
1. 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。(Spring 事务实现源码)
2. 线程间数据隔离(本文使用)
3. 进行事务操作,用于存储线程事务信息。
4. 数据库连接,Session会话管理。(MyBatis源码)
2.2 ThreadLocal数据隔离分析
分析ThreadLocal对象的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);
}
发现有一个map,这个Map是Thread类的成员变量threadLocals
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
存入value时是以ThreadLocal为key,将值保存到threadLocals这个map中,因为每个Thread都有一个Map,所以每个Thread取到的值都不同。
2.3 为什么ThreadLocal中用到了弱引用?
这是为了防止内存泄露而采用的设计。ThreadLocal与ThreadLocalMap里的entry对象是弱引用,
当回收ThreadLocal的强引用后,因为entry的key与ThreadLocal是弱引用,所以会被gc。为了避免value内存泄露,最好使用remove方法。
https://www.bilibili.com/video/BV1N741127FH?p=7
3. 使用ThreadLocal计数解决科室模拟死循环问题
CheckRoom.java
@Slf4j(topic = "room")
public class CheckRoom extends Thread{
ThreadLocal<Integer> local = ThreadLocal.withInitial(()->0);
private final Integer totalCheck;
private final Long checkTime;
private final String roomName;
public CheckRoom(Integer totalCheck, Long checkTime, String roomName) {
this.totalCheck = totalCheck;
this.checkTime = checkTime;
this.roomName = roomName;
}
@Override
public void run() {
while (true) {
// 判断实验终止条件
if(local.get() >= totalCheck) {
log.info("{}体检人数达到{}人,实验结束!", this.roomName, totalCheck);
break;
}
log.info("{}体检开始...", this.roomName);
try {
Thread.sleep(this.checkTime);
log.info("{}体检完成...", this.roomName);
local.set(local.get() + 1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试程序
public class Main {
public static void main(String[] args) {
Integer totalNum = 12;
CheckRoom eyeRoom = new CheckRoom(totalNum, 1000L, "眼科");
CheckRoom caixue = new CheckRoom(totalNum, 2000L, "采血室");
CheckRoom caichao = new CheckRoom(totalNum, 6000L, "彩超室");
eyeRoom.start();
caixue.start();
caichao.start();
}
}
当然,本实验的例子没有串联起来,实验demo和正式项目不同。