threadlocal
Threadlocal
最近在做Spark项目的时候,使用Scala语言,遇到了多线程的问题,想到之前理解的ThreadLocal的数据隔离的意义,在使用后,以为解决了,但是还是遇到了多线程问题;这是我很困惑,后来在做了几个Demo后,才彻底明白ThreadLocal的意义。
写在前面
以前,在看很多博客的时候,总是有很多作者在描述ThreadLocal的作用是数据隔离,并且是每一个线程复制了一份,每个线程的访问的数据都是不受其他线程影响的。其实,这句话前半句是对的,ThreadLocal的确是数据的隔离,但是并非数据的复制,而是在每一个线程中创建一个新的数据对象,然后每一个线程使用的是不一样的。
ThreadLocal详解
我们首先来看一段代码,读者可以猜一猜这个运行结果是什么。
package cn.edu.hust.constant;
import java.util.Random;
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static void main(String[] args) throws InterruptedException {
final Person p=new Person();
//这样做其实就是在操作同一个对象,如果需要实现多线程应该像下下面的注释一样,这样就针对于每一个线程创建一个独立的Person对象
final ThreadLocal<Person> t=new ThreadLocal<Person>(){
public Person initialValue() {
//return new Person();
return p;
}
};
p.setName("小明");
for(int i=0;i<3;i++)
{
new Thread(new Runnable() {
@Override
public void run() {
t.set(p);
t.get().setName(new Random().nextint(100)+"");
System.out.println(t.get().getName()+"=="+t.get());
}
}).start();
}
Thread.sleep(1000);
System.out.println(p.getName());
}
}
这里给出运行的答案,其实这个程序每一次运行的结果都是不一样的。为什么会这样的?
我们知道,ThreadLocal是数据隔离,如果按照以前的理解,应该是最后的输出一定是不会改变的,也就是“小明”;其实,这里不一样,是因为每一个线程保存的Person对象的地址都是一样的,这里的运行结果可以看出。
如果是这样就是不能实现多线程的共享。下面,我们再来看一份代码:
package cn.edu.hust.constant;
public class TestNum {
// ①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal<integer> seqNum = new ThreadLocal<Integer>() {
public Integer initialValue() {
return 0;
}
};
// ②获取下一个序列值
public int getNextNum() {
seqNum.set(seqNum.get() + 1);
//System.out.println();
return seqNum.get();
}
public ThreadLocal<Integer> getThreadLocal()
{
return seqNum;
}
public static void main(String[] args) {
TestNum sn = new TestNum();
// ③ 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 TestNum sn;
public TestClient(TestNum 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() + "]"+sn.getThreadLocal().get().hashCode());
}
}
}
}
这里可以看出,在每一个线程的确是没有互相影响,但是,我们知道,Integer是一个final修饰的类,在每次操作后,都会创建一个新的Integer,每次线程都是会在初始值的基础上创建,也就是每一次的地址都是不一样,然后进行操作。
ThreadLocal数据结构
ThreadLocal的数据结构如下:
static class ThreadLocalMap {
/**
* The entries in this hash map extend weakreference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
ThreadLocal内部有一个ThreadLocalMap,可以从名字看出,它是一个HashMap,我们在使用的键值对多少保存在这个Map里面。让我们来看一下,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);
}
从上面的代码可以看出,这里的方法是以当前线程为Key,程序员自定义的value为值,然后放置在ThreadLocal的内部ThreadLocalMap中。
ThreadLocal在框架中的例子
在著名的框架Hiberante中,我们来看一下数据库连接的代码:
private static final ThreadLocal threadsession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getsessionfactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
在建立每一个数据库会话,都是通过一个Session工厂创建,然后返回给当前线程;这里对于ThreadLocal的使用时,通过SessionFactory创建一个Session的对象,然后设置在ThreadLocal的内部类中ThreadLocalMap中,并发所说的数据复制。
总结
在理解ThreadLocal后,给我的最大感觉是,在看完博客后,一定要自己动手实现,不能听之任之,要真正弄懂,对于ThreadLocal,并非对象的复制,而是针对于每一个线程创建一份新的对象设置在其中。
相关阅读
假如设A 和 B 的局域网 IP 相同(192.168.31.11),当他们同时访问百度服务器的时候,百度服务器如何区分哪个是 A,哪个是 B 呢?解决方案:端
在多线程的场景中,我们需要保证数据安全,就会考虑同步的方案,通常会使用synchronized或者lock来处理,使用了synchronized意味着内核
一、路由追踪程序traceroute/tracert Traceroute是Linux和Mac OS等系统默认提供的路由追踪小程序,Tracert是Windows系统默认提供
本文来自 网易云社区 。为大家分享一篇网易教育产品部韩坤芳关于金字塔原理的文章。希望一起交流探讨,共同进步提升。工作的时候
裂变式传播,最重要是抓住用户心理,提供足够的传播动力,在每个环节降低用户操作门槛。一、裂变式传播定义我第一次接触“裂变”这个概