假设现在让你去实现一个连接类,要支持多个线程访问,同时每个线程独占一个Connection?
这时候你会怎么实现?
也许这会你想到了给每个线程传递一个Connection instance。
这样是可以实现。但你想过没有。每次只传输一个同样配置的实例,只是引用地址不一样。是不是从设计的角度很不优雅呢?而且还容易出错,不变性也被破坏了。
于是你又想,那就把这个Connection搞成同步的。让大家排队来用一个Connection。
这种做法显然不满足需求啊。每人用一个,总比大家用一个好吧。
于是Josh Bloch 和 Doug Lea这两个神牛开始考虑一种新的思路。
既然其它都一样,只是需要一个线程一个实例而已。那我们就开发一个
基于线程的一个本地变量的支持类。(ps:这两个人设计了ThreadLocal)
这样以后遇到类似的需求,开发者们就可以使用我们的这个类了。
这个类叫什么名字呢?既然和线程强绑定,而且是线程独有的,那就是本地的喽。好吧,那就你叫ThreadLocal啦?Josh Bloch一边敲着键盘一边问Doug Lea。Doug Lea想了下,嗯,这个名字不错,就这样叫吧。而且这个类老实讲并发占的比重不大,就听Josh Bloch的吧。。。。。(lea是并发包的主要缔造者,你懂的,而且threadlocal位于java.lang包下)
于是这两货就兴致勃勃地开始开发这个类了。
既然是一个变量。我们那些普通的变量都有两个操作,写值和读值。那我们这个封装类也得提供这两个操作。这样看起来才像一个变量该有的样子。
于是lea兄果断的写了这么两个个方法:
public T get() {
//todo
}
public void set(T value) {
}
然后他想,这设置进来的值还没法指定类型。像这种该存到什么数据结构中呢?他脑中迅速闪过了map这个三个字母。M......A......P,没错,万能的map,可以存储很多种类型啊。
嗯,那就用map来搞存储吧。
于是lea和josh决定使用map来存储真实的变量实例。
这时候问题又来了。难道就用现在有的那些map来搞吗? 不行,我们还是要内部再搞一个map,就叫ThreadLocalMap。这个map反正也不对外提供服务就把他放在ThreadLocal类内部吧,搞成个静态内部类算了。
于是他们在threadlocal内部新建了一个ThreadLocalMap的内部静态类。lea熟练的把现在有的那个hashmap代码复制了进来。map的结构都是相似的。
map内部其实就是一个数组,比如要有下面的这些代码:
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
还有capacity等等:
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
另外还要有一个内部的entry类来承载每一个key value的存储。
自然要设计一个entry实体类。
static class Entry {
}
由于map中的内容一直hold在里边,会造成gc回收的一些问题,我们要把这个entry设计成一个弱引用。好,改成下面这样:
static class Entry extends WeakReference<ThreadLocal> {
}
嗯,就这样搞。按理说这里边我们应该加两个属性,一个叫key,一个叫value。传统的map都喜欢这么做。但这里我们其实不需要key,只需要存储value,也就是那个变量的值。
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
嗯,就这么搞吧,里边加个属性叫value。然后通过构造函数传入进来。
既然是一个map。最重要的两个方法自然少不了。
那就是set(key,value)和get(key),类似这样的。
get
private Entry getEntry(ThreadLocal key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
set:
private void set(ThreadLocal key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
其实最核心的就是你得去new 一个entry然后设置到table数组中,就是下面这句:
tab[i] = new Entry(key, value);
ok,现在map雏形差不多了。我们开始编写threadlocal类中的get set方法吧。
数据结构都有了,接下来的事情就好搞了。
先写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用来存储value。
key就是threadlocal现在的实例引用,然后存储到map中。
你也许发现了map是从哪里获取来的?其实每个线程都对应一个map,就是threadlocalmap。只要你通过
Thread.currentThread()
拿到当前线程实例,然后传入下面这个方法就可以从thread类中拿到map。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
好,继续写get方法:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
既然set我们已经搞清楚,get就很简单了。通过threadlocal为key去当前线程对应的map中查找到value返回即可。
好,现在这个类已经设计的差不多了。
写上我们的大名吧:
* @author Josh Bloch and Doug Lea
瞧瞧我两好叼,再把版本加上吧。
* @since 1.2
Cheers!
上自拍:
i am josh bloch,嘿嘿,collection framework就是我搞的。
i am doug lea,并发包就是我搞的
好,继续,那ThreadLocal究竟怎么用呢?
public Class ConnectionManager {
//创建一个私有静态的并且是与事务相关联的局部线程变量
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>;
public static Connection getConnection(){
//获得线程变量connectionHolder的值conn
Connection conn = connectionHolder.get();
if (conn == null){
//如果连接为空,则创建连接,另一个工具类,创建连接
conn = DbUtil.getConnection();
//将局部变量connectionHolder的值设置为conn
connectionHolder.set(conn);
}
return conn;
}
}
------------------
上面这段是ThreadLocal的一个比较典型的使用场景。就是数据库的Connection。
你看了这段代码后也许并不能感受到他的运行机制。下面上一个例子:
public class SequenceNumber {
//通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {
public Integer initialValue() {
return 0;
}
};
//获取下一个序列值
public int getNextNum() {
seqNum.set(seqNum.get() + 1);
return seqNum.get();
}
public static void main(String[] args) {
SequenceNumber sn = new SequenceNumber();
//3 个线程共享sn,各自产生序列号
TestClient t1 = new TestClient(sn);
TestClient t2 = new TestClient(sn);
TestClient t3 = new TestClient(sn);
t1.start();
t2.start();
t3.start();
}
private static class TestClient extends Thread {
private SequenceNumber sn;
public TestClient(SequenceNumber sn) {
this.sn = sn;
}
public void run() {
for (int i = 0; i < 3; i++) {//④每个线程打出3个序列值
System.out.println("thread[" + Thread.currentThread().getName() +
"] sn[" + sn.getNextNum() + "]");
}
}
}
}
运行结果是这样的:
thread[Thread-0] sn[1] thread[Thread-0] sn[2] thread[Thread-0] sn[3] thread[Thread-1] sn[1] thread[Thread-1] sn[2] thread[Thread-1] sn[3] thread[Thread-2] sn[1] thread[Thread-2] sn[2] thread[Thread-2] sn[3]
也许你发现了,每个线程所产生的序列号都是有序增长的。说明每个线程都是用的自己的副本。
此时你再仔细想想,假如没有ThreadLocal,你会怎么实现这样的功能呢?如果不通过参数传递,恐怕还真没有其它好的办法。
所以以后遇到类似的需求,你就可以通过ThreadLocal把你要声明的变量做一个包装,然后通过set get来操作就好了。
本文分享自 ImportSource 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!