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和正式项目不同。

posted @ 2022-03-11 18:49  cee_nil  阅读(45)  评论(0编辑  收藏  举报