ThreadLocal学习(一)

丶灬走出姿态 提交于 2020-02-02 03:24:21

Java:ThreadLocal有什么用呢?

 ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。ThreadLocal可以用来解决多线程程序的并发问题,ThreadLocal并不是一个Thread,而是Thread的局部变量,当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。

ThreadLocal的应用场景?

在Java的多线程编程中,为保证多个线程对共享变量的安全访问,通常会使用synchronized来保证同一时刻只有一个线程对共享变量进行操作。这种情况下可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。最常见的ThreadLocal使用场景为用来解决数据库连接、Session管理等。在下面会例举几个场景。 

ThreadLocal类中的方法:(JDK5版本之后支持泛型)

    void set(T value)
          将此线程局部变量的当前线程副本中的值设置为指定值
    void remove()
          移除此线程局部变量当前线程的值
    protected T initialValue()
          返回此线程局部变量的当前线程的“初始值”
    T get()
          返回此线程局部变量的当前线程副本中的值

ThreadLocal的原理:

   ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本

自己模拟ThreadLocal:

public class SimpleThreadLocal{
       private Map valueMap=Collections.synchronizedMap(new HashMap());
       public void set(Object newValue){
          valueMap.put(Thread.currentThread(),newValue);//键为线程对象,值为本线程的变量副本
       }
       public Object get(){
          Thread currentThread=Thread.currentThread();
          Object o=valueMap.get(currentThread);//返回本线程对应的变量
          if(o==null&&!valueMap.containsKey(currentThread)){
              //如果在Map中不存在,放到Map中保存起来
              o=initialValue();
              valueMap.put(currentThread,o);
          }
          return o;
       }
       public void remove(){
           valueMap.remove(Thread.currentThread());
       }
       public void initialValue(){
           return null;
       }
    }

使用ThreadLocal的具体例子:

public class SequenceNumber{
      //通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
      private static ThreadLocal<Integer> seNum=new ThreadLocal<Integer>(){
       protected Integer initialValue(){
           return 0;
        }
      }
      public Integer getNextNum(){
           seNum.set(seNum.get()+1);
           return seNum.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(){
              //每个线程打印3个序列号
              for(int i=0;i<3;i++){
                  System.out.println("thread["+Thread.currentThread().getName()+",sn["+sn.getNextNum()+"]");
              }
          }
      }
   }

通过 ThreadLocal 封装了一个 Integer 类型的 numberContainer 静态成员变量,并且初始值是0。再看 getNumber() 方法,首先从 numberContainer 中 get 出当前的值,加1,随后 set 到 numberContainer 中,最后将 numberContainer 中 get 出当前的值并返回。

是不是很恶心?但是很强大!确实稍微饶了一下,我们不妨把 ThreadLocal 看成是一个容器,这样理解就简单了。所以,这里故意用 Container 这个单词作为后缀来命名 ThreadLocal 变量。

运行结果如何呢?看看吧。

Thread-0 => 1
Thread-0 => 2
Thread-0 => 3
Thread-2 => 1
Thread-2 => 2
Thread-2 => 3
Thread-1 => 1
Thread-1 => 2
Thread-1 => 3

每个线程相互独立了,同样是 static 变量,对于不同的线程而言,它没有被共享,而是每个线程各一份,这样也就保证了线程安全。 也就是说,TheadLocal 为每一个线程提供了一个独立的副本!

ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。归纳了两点:
1。每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
2。将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行
的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。

ThreadLocal的应用场合,我觉得最适合的是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。

小结

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

 
推荐阅读:
ThreadLocal 那点事儿https://my.oschina.net/huangyong/blog/159489
https://my.oschina.net/huangyong/blog/159725
 
 
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!