赞
踩
总结:
ThreadLocal.ThreadLocalMap threadLocals = null;
)当你public static void main(String[] args) { ThreadLocal<String> tl = new ThreadLocal<>(); new Thread(new Runnable() { @Override public void run() { tl.set("测试一号"); System.out.println(tl.get());//测试一号 } }).start(); new Thread(new Runnable() { @Override public void run() { tl.set("测试二号"); System.out.println(tl.get());//测试二号 } }).start(); System.out.println(tl.get());//null }
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看源码发现会先获取当前线程,再获取当前线程的ThreadLocalMap。重点来了!!!map.set(this, value);
set的key是this,而调用这个set方法的对象是tl,所以key是ThreadLocal对象tl,value是你要传的值,说明你也可以多放几个ThreadLocal对象(这里的set并不是把值set到ThreadLocal对象中,容易让人产生误解)。
再一个问题是
//这个是上面set(value)方法里面的set(this,value)方法 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(); }
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
我们发现了这是个弱引用,弱引用所指向的对象如果只有当前弱引用,无别的引用,那gc的时候直接会清除掉那个堆中的对象,避免了内存泄漏(假设是个强引用,那tl为null了,但map中的key还指向那个堆中ThreadLocal,导致不释放ThreadLocal对象,造成内存泄漏,所以是弱引用),一旦堆中的ThreadLocal对象没有了强引用只有这个弱引用了,那就可以清除掉堆中ThreadLocal了,清除掉后key将会变为null,但是整个Entry还有value没有释放,所以我们最好remove掉不用的键值对,虽然我们在get,set的时候会自动清理掉key为null的,但不用最好第一时间清理掉,防止内存泄漏。
再一个问题就是线程池中的线程被多次使用,所以线程池中每次回收回来的线程都会有个操作将线程中的ThreadLocalMap属性置为null,这也是为了防止上一个线程留下的脏数据。
再一个例子:
spring中@transaction事务中的数据库连接connection,该连接是每个线程用的同一个,该连接就是用的ThreadLocal.
扩展:
引用类型分为强软弱虚
强引用:不会被gc掉,就算内存溢出OOM也不会被干掉
软引用:空间充足时gc不会被干掉,堆空间不足时发生gc就会干掉
弱引用:只要gc就会干掉
虚引用:get()方法获取不到值,主要用来作为一个通知管理直接内存的,当虚引用的对象被清除不会直接清掉,而是会加入一个队列,后续操作一般是将这个虚引用所指向的直接内存释放掉,用的很少
测试的有点乱,ShardData2就是我们需要放到一个线程一个实例的类,这个类的定义是重点 package com.heima.test; import java.util.Random; public class TestThreadlocal {//专门用来测试的 public static void main(String[] args) { // SharedData sharedData = new SharedData();//想要实现拿过来一个数据类后然后进行多线程操作,可以对自定义的优雅的数据集进行添加属性,编写复杂的方法,主要是对拿过来的数据进行封装到这个类中,然后就可以进行操作了! MyThread myThread = new MyThread();//这里面的属性会被共享,因为下面的线程都是用这一个实例创建的 Thread thread1 = new Thread(myThread); Thread thread2 = new Thread(myThread); thread1.start(); thread2.start(); } } class MyThread implements Runnable{//自定义线程类 private ThreadLocal<SharedData> threadLocal=new ThreadLocal<SharedData>(); //SharedData sharedData;//这个是有状态的bean,因为里面存数据;因为是通过同一个子类开启的2个线程所以放在这里会被共享,存在多线程问题,所以你可以放到run方法中变成每一个线程创建一个 //虽然你可以放到ThreadLocalMap中,但是多线程获取到的时候,是单独存储了一份引用,但是根据这个引用再去找到堆中的对象的时候,这个堆中的对象的属性可以被其余线程更改,所以说我们需要每一个线程都拿到单独的一个实例,然后在该线程中只操作这一份实例 //如果这个变量可以直接拿来用拿来读,不做改变,那这没问题,一旦改变就会有问题!这也是多线程安全的本质,你只读肯定不会有线程安全问题,主要是根据业务肯定有改的地方 //例如像dao层获取数据库连接的时候就用的ThreadLocal,每一个访问的线程都拥有自己单独的一个数据库链接实例,并且是不做更改的,只是使用(里面有链接数据库地址,连接用户名,连接密码等等) //如果不改变里面的值,也就是说不存在线程安全问题,那么设计成单例更好 //如果防止了结束上一个线程关闭了连接,导致下一个线程也不能用了,但是想起我们以前是通过每次开启一个线程操作数据库的时候,是new一个数据库链接对象的方式进行操作的,需要频繁的new和关闭链接,然而 //放到线程内部每次都new一个对象也是一样,目前来看没看到ThreadLocal的作用 //其实想了一下:如果类是没有状态的,就算有状态存数据,但是只是去读取数据,不做修改,也不会出现线程安全问题,所以用一首单例岂不更好 //单例可以在多线程之间通信,减少实例的数量,节约资源 //说到多线程之间可以通信,完全可以用有状态的bean来存数据并设计成单例的,然后你需要严格的控制对这个有状态的bean进行操作,例如用锁等进行同步处理,避免执行到一半,让另一个线程又操作了这个单例对象,使得最终的结果出现错误 //除了用锁还可以用这种ThreadLocal方式 //对于网站计数器对象的例子:首先存数据了有状态的,也设计成单例,但是一想有状态的bean,如果多线程来访问,还是会存在问题,所以需要用到同步 //一个单例模式的方法可以同时被多个线程处理,多个线程如果不是同时处理这一个对象的共有属性,则不会出现线程问题,如果两个线程同时访问同一个方法的时候,如果这个方法中没有都有的属性,则不需要加锁,反之则需要加锁。 // public MyThread(SharedData sharedData){ // this.sharedData = sharedData; // } @Override public void run() { // SharedData sharedData =new SharedData(); Random random = new Random(); int a=random.nextInt(100); // sharedData.setNum(a); System.out.println(Thread.currentThread().getName()+"初始的值为:"+a); // threadLocal.set(sharedData);//其实key就是threadLocal对象,值这里是个对象的引用,所以说这里的对象的具体的一个属性值是会变的 ModuleA moduleA = new ModuleA(); //moduleA.doSomethingA(threadLocal.get()); ModuleB moduleB = new ModuleB(); // moduleB.doSomethingB(threadLocal.get()); /// SharedData2.getInstance().setNum(a);//优雅方式对每个线程中独有的那个线程级变量进行赋值,因为每一个线程一个独立的对象 moduleA.doSomethingA2(); moduleB.doSomethingB2(); } } class SharedData{//共享数据类 private int num; public int getNum() { return num; } public void setNum(int num) { this.num = num; } } class ModuleA{//对共享数据进行操作的A业务类 public void doSomethingA(SharedData sharedData){ sharedData.setNum(sharedData.getNum()+1); System.out.println(Thread.currentThread().getName()+"A业务看到的共享数据值为"+sharedData.getNum()); } public void doSomethingA2(){//优雅方式的共享数据类获取值 System.out.println(Thread.currentThread().getName()+"A2业务看到的共享数据值为"+ SharedData2.getInstance().getNum()); } } class ModuleB{//对共享数据进行操作的B业务类 public void doSomethingB(SharedData sharedData){ System.out.println(Thread.currentThread().getName()+"B业务看到的共享数据值为"+sharedData.getNum()); } public void doSomethingB2(){//优雅方式的共享数据类获取值 System.out.println(Thread.currentThread().getName()+"B2业务看到的共享数据值为"+ SharedData2.getInstance().getNum()); } } class SharedData2{//优雅的共享数据类:外界只需调用方法使用即可,外界根本看不到ThreadLocal,本类就是线程单例:在每一个线程中只有一个实例 private SharedData2(){};//构造方法私有,外界无法生成该类对象实例,只能通过后面的公有静态方法生成实例 //找到问题了,这个首先我们不是只创建这一个实例,所以没必要设置这一个实例,其次这是个静态的成员变量,是所有对象共享的,所以我出现了多线程问题 // private static SharedData2 sharedData2 = null;//这里弄成成员变量跟单例一样,这也是懒汉模式,为了在后面只生成这一个实例 private static ThreadLocal<SharedData2> threadLocal= new ThreadLocal<SharedData2>();//放线程独有变量 public static SharedData2 getInstance(){//想获得该类对象只能通过该方法获得 SharedData2 sharedData2=threadLocal.get();//先从当前线程的threadLocal中获取,看看是否有,有就直接返回,没有就需要创建一个对象然后存到threadLocal if(sharedData2==null){ sharedData2=new SharedData2(); threadLocal.set(sharedData2); } return sharedData2; } private int num;//私有成员变量,在堆里,堆里都是一个一个的实例,一个实例一个变量,所以没有线程安全问题 public int getNum() { return num; } public void setNum(int num) { this.num = num; } }
第一个例子:数据库连接
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
第二个例子:一个请求过来,会带有一堆的c参数(可以理解为客户端的一些标志),我们的应用处理过程中,大部分地方又不需要关心该参数,可能在某个请求他人接口的时候需要了,如果我们把所有代码都带上这个c参数,那么未免代码看着太过丑陋,这种情况下,我们可以构建一个filter,在请求过来的时候,在filter中将c参数放置到ThreadLocal中,在整个调用链中如果需要使用,直接从ThreadLocal中获取即可。
另外一个例子是动态数据源的使用,我们可以使用ThreadLocal来保证当次线程调用中只使用一种数据源
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。