用普通变量在多线程环境下的例子
public class ThreadLocalTest {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(10,()->{
System.out.println("每个线程的对于值的初始化完成");
});
EasyClass easyClass = new EasyClass();
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(()->{
easyClass.setAnInt(finalI);
easyClass.setString(String.valueOf(finalI*2));
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"我拿了 int="+easyClass.getAnInt());
System.out.println(Thread.currentThread().getName()+"我拿了 String="+easyClass.getString());
System.out.println("=================");
},String.valueOf(i)).start();
}
}
}
class EasyClass{
int anInt;
String string;
public int getAnInt() {
return anInt;
}
public void setAnInt(int anInt) {
this.anInt = anInt;
}
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
}
- 我们通过CyclicBarrier放大了线程并发时可能出现的共享变量获取异常的问题,可以看到最后结果都是9和18
下面使用ThreadLocal的方式
public class ThreadLocalTest {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(10,()->{
System.out.println("每个线程的对于值的初始化完成");
});
EasyClass easyClass = new EasyClass();
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(()->{
easyClass.setInt(finalI);
easyClass.setString(String.valueOf(finalI*2));
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"我拿了 int="+easyClass.getInt());
System.out.println(Thread.currentThread().getName()+"我拿了 String="+easyClass.getString());
System.out.println("=================");
},String.valueOf(i)).start();
}
}
}
class EasyClass{
ThreadLocal<Integer> threadLocal1 = new ThreadLocal<>();
ThreadLocal<String> threadLocal2 = new ThreadLocal<>();
public int getInt(){
return threadLocal1.get();
}
public void setInt(int i){
threadLocal1.set(i);
}
public void setString(String q){
threadLocal2.set(q);
}
public String getString(){
return threadLocal2.get();
}
}
- 这是一个简单的ThreadLocal的例子,能够使想要的变量线程私有化,比如并发共享变量。
- 这里通过一个CyclicBarrier进行线程阻塞,待全部线程设置了值之后,才对变量进行查看。验证变量线程私有
简单的了解ThreadLocal的用处,那么他是如何实现的?
ThreadLoacl中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);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
- 可以看到,set方法先获得当前线程,然后通过getMap方法得到一个map对象。(map对象在哪里?)
- 如果map不为空,则替换或添加到key为当前ThreadLocal对象 对应的位置
我们继续查看 threadLocals
class Thread implements Runnable {
····
ThreadLocal.ThreadLocalMap threadLocals = null;
····
}
- 可以看到 threadLocals是一个ThreadLocalMap 类型的,默认为null,是线程中内置的;
- 同时这个ThreadLocalMap 是ThreadLocal的一个静态内部类
- 在ThreadLocalMap中,也是用Entry来保存K-V结构数据的。但是Entry中key只能是ThreadLocal对象,这点被Entry的构造方法已经限定死了。
- 同时可以看到Entry继承自WeakReference,但只有Key是弱引用类型的,Value并非弱引用。
- ThreadLocalMap解决hash冲突的方式是采用线性探测的方式
- 同时需要注意由于ThreadLocalMap健为弱引用,value不是弱引用,可能导致内存泄漏
这张图可以比较清除它的一个调用情况,每个线程都有维护自己的Map,如果有ThreadLocal则会放入Entry数组中,这个保证了变量私有化