转载

大神们是怎么使用ThreadLocal的?

这篇文章是关于ThreadLocal的第三篇文章。本文将挑选一些主流的Java开源框架,从源码上分析,大神们是如何使用ThreadLocal的,学习他们的设计思想。

大家可以直接打开github,搜索相应的项目,然后在项目中搜索相关的类,即可看到源代码。

Quartz是一个非常知名的开源任务调度系统。

我们要看的源码是Quartz的 SimpleSemaphore 这个类。它是一个信号量的实现,在生产者-消费者模型里,信号量代表的就是队列里有多少item需要处理。

在信号量的模型里面有一个“等待”操作。当消费者消费完后,会轮询等待。 SimpleSemaphore 有一个获取锁的方法 obtainLock() ,我们要看的也是这个方法的内部代码:

大神们是怎么使用ThreadLocal的?

92行的while循环就是去进行轮询操作,while里面的locks是一个 HashSet ,为true代表这个lockName对应的锁正在被别的线程持有,所以当前线程需要等待。

我们看到,在while循环的外层86行,有一个判断,其实是用到了ThreadLocal。

大神们是怎么使用ThreadLocal的?

这个外层的判断起什么作用呢?其实是 判断当前线程是否已经持有了这个锁 。如果持有了,那就直接跳到最后return true了。因为同一个线程,可能有多个程序片段会调用这个获取锁的方法。

可以看到,使用ThreadLocal可以非常高效地判断当前线程的状态,可以快速检测出当前线程是否已经获取了锁,避免了后续锁的检测和争用。

Mybatis

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

关注我的公众号,和我一起成长~

大神们是怎么使用ThreadLocal的?
原文  https://juejin.im/post/5ef0be7de51d4573e71f3309
正文到此结束
Loading...