线程数据共享-ThreadLocal

引导:
同一个线程共享共同的数据,我们可以将当前线程放到map中,同一个线程取自己对应的value值即可。

public static Integer data=0;
    public static Map<Thread,Integer> map=new HashMap<Thread,Integer>();
    public static void main(String[] args) {
        for(int i=0;i<2;i++){
        new Thread(new Runnable() {
            @Override
            public void run() {
                data=new Random().nextInt();
                map.put(Thread.currentThread(),data);
                System.out.println(Thread.currentThread().getName()+" data="+data);
                new A().get();
                new B().get();
            }
        }).start();
        }
    }

    static class A{
        public void get(){
            System.out.println("A  data:"+map.get(Thread.currentThread()));
        }
    }   
    static class B{
        public void get(){
            System.out.println("B  data:"+map.get(Thread.currentThread()));
        }
    }

扩展,如果我想在同一个线程中共享多个数据,我们应该如何处理呢?
有的小伙伴会想到,可以用包装类,把包装类放到map中。
JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序,ThreadLocal并不是一个Thread,而是Thread的局部变量。
ThreadLocal的接口方法
ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:
void set(Object value)
public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 1.5 新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
protected Object initialValue()
返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中定义了一个ThreadLocalMap,每一个Thread中都有一个该类型的变量——threadLocals——用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。

public class TheardTestController {

    private static ThreadLocal<Person> map=new ThreadLocal<Person>();
    /**
     * ThreadLocal实现数据共享
     * */
    public static void main(String[] args) {
        for(int i=0;i<2;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int i = new Random().nextInt();
                    Person.getInstance().setName("name"+i);
                    Person.getInstance().setAge("age"+i);
                    map.set(Person.getInstance());
                    new A().get();
                    new B().get();

                }
            }).start();

        }

    }

    static class A{
        public void get(){
            System.out.println(Thread.currentThread().getName()+"  A from data  name="+map.get().getName()+"  ,age="+map.get().getAge());
        }
    }

    static class B{
        public void get(){
            System.out.println(Thread.currentThread().getName()+"  B from data  name="+map.get().getName()+"  ,age="+map.get().getAge());
        }
    }
}


class Person{
    private Person(){}
    public static Person getInstance(){
        Person instance=map.get();
        if(instance==null){
            instance=new Person();
            map.set(instance);
        }
    return instance;
    }
    private static ThreadLocal<Person> map=new ThreadLocal<Person>();

    public String name;
    public String age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAge() {
        return age;
    }
    public void setAge(String age) {
        this.age = age;
    }

}
}

每个线程都有一份变量,当这个变量没有线程访问时,如何释放掉呢?
在线程结束时可以调用ThreadLocal.clear()方法(思路:我们如何知道线程什么时候结束,线程以后回掉方法)。
不调用也可以,因为线程结束后会自动释放相关的ThreadLocal变量(当除了threadLocal引用外,没有别的地方引用,会释放)。
ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
线程同步是多个线程排队访问共享数据,通过对象的锁机制保证同一时间只有一个线程访问变量。是“用时间换空间”
ThreadLocal为每一个线程的都提供了一份变量,因此可以同时访问而互不影响。是“用空间换时间”。
下面的实例能够体现Spring对有状态Bean的改造思路:
非线程安全的代码:

public class TopicDao {
private Connection conn;①一个非线程安全的变量
public void addTopic(){
Statement stat = conn.createStatement();②引用非线程安全变量
…
}
}

线程安全的代码:

import java.sql.Connection;
import java.sql.Statement;
public class TopicDao {
//①使用ThreadLocal保存Connection变量
private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();
public static Connection getConnection(){
//②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection,
并将其保存到线程本地变量中。
}
public void addTopic() {
//④从ThreadLocal中获取线程对应的Connection
Statement stat = getConnection().createStatement();
}
}

不同的线程在使用TopicDao时,这样,就保证了不同的线程使用线程相关的Connection,而不会使用其它线程的Connection。因此,这个TopicDao就可以做到singleton共享了。
当然,这个例子本身很粗糙,将Connection的ThreadLocal直接放在DAO只能做到本DAO的多个方法共享Connection时不发生线程安全问题,但无法和其它DAO共用同一个Connection,要做到同一事务多DAO共享同一Connection,必须在一个共同的外部类使用ThreadLocal保存Connection。

总结
ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值