这篇文章是关于ThreadLocal的第三篇文章。本文将挑选一些主流的Java开源框架,从源码上分析,大神们是如何使用ThreadLocal的,学习他们的设计思想。
❝
大家可以直接打开github,搜索相应的项目,然后在项目中搜索相关的类,即可看到源代码。
❞
Quartz是一个非常知名的开源任务调度系统。
我们要看的源码是Quartz的 SimpleSemaphore
这个类。它是一个信号量的实现,在生产者-消费者模型里,信号量代表的就是队列里有多少item需要处理。
在信号量的模型里面有一个“等待”操作。当消费者消费完后,会轮询等待。 SimpleSemaphore
有一个获取锁的方法 obtainLock()
,我们要看的也是这个方法的内部代码:
92行的while循环就是去进行轮询操作,while里面的locks是一个 HashSet
,为true代表这个lockName对应的锁正在被别的线程持有,所以当前线程需要等待。
我们看到,在while循环的外层86行,有一个判断,其实是用到了ThreadLocal。
这个外层的判断起什么作用呢?其实是 「 判断当前线程是否已经持有了这个锁 」 。如果持有了,那就直接跳到最后return true了。因为同一个线程,可能有多个程序片段会调用这个获取锁的方法。
可以看到,使用ThreadLocal可以非常高效地判断当前线程的状态,可以快速检测出当前线程是否已经获取了锁,避免了后续锁的检测和争用。
Mybatis不用多说,搞Java的应该都听过或者用过。我们今天要介绍的是它的SqlSessionManager。
Mybatis是一个持久化框架。持久化框架,必然会面临事务的问题。我们的数据库(比如MySQL)可以保证本地事务,但也要求必须在同一个连接才行。
应用程序使用MyBatis,可能会在多个程序片段去访问数据库,做一些增删改查的操作。它们可能需要在同一个事务里面。
举个例子,我们修改完订单状态后,可能还需要修改积分,它们应该在同一个事务里。
Mybatis使用SqlSessionManager保证了我们同一个线程取出来的连接总是同一个。它是如何做到的呢?其实很简单,就是内部使用了一个ThreadLocal。
然后所有的创建连接、取连接都是通过这个ThreadLocal变量的get/set方法进行操作。
private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();
// 创建连接
public void startManagedSession() {
this.localSqlSession.set(openSession());
}
// 取连接
@Override
public Connection getConnection() {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot get connection. No managed session is started.");
}
return sqlSession.getConnection();
}
复制代码
其实ThreadLocal使用起来是很简单的,这也是ThreadLocal设计的初衷。
使用ThreadLocal,可以保存线程的状态,使得多个程序片段可以很方便地得到当前线程的数据,而不会对其它线程造成影响,也不需要上锁同步。
所以,使用ThreadLocal可以“避免”一些多线程问题,开发安全高效的应用程序。
我是Yasin,一个有颜有料又有趣的程序员。
微信公众号:编了个程
个人网站:https://yasinshaw.com
关注我的公众号,和我一起成长~