ThreadLocal 与 static 变量

余生颓废 提交于 2020-08-13 18:20:41

参考文章:ThreadLocal 与static变量

ThreadLocal是为解决多线程程序的并发问题而提出的,可以称之为线程局部变量。与一般的变量的区别在于,生命周期是在线程范围内的。
static变量是的生命周期与类的使用周期相同,即只要类存在,那么static变量也就存在。
那么一个 static 的 ThreadLocal会是什么样的呢?

看下面一个例子,

public class SequenceNumber {  
 private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){  
  public Integer initialValue(){  
   return 0;  
  }  
 };  
   
 public int getNextNum(){  
  seqNum.set(seqNum.get() + 1);  
  return seqNum.get();  
 }  
   
 public static void main(String[] args){  
  SequenceNumber sn = new SequenceNumber();  
  TestClient t1  = new TestClient(sn);  
  TestClient t2  = new TestClient(sn);  
  TestClient t3  = new TestClient(sn);  
    
  t1.start();  
  t2.start();  
  t3.start();  
    
  t1.print();  
  t2.print();  
  t3.print();   
 }  
   
 private static class TestClient extends Thread{  
  private SequenceNumber sn ;  
  public TestClient(SequenceNumber sn ){  
   this.sn = sn;  
  }  
    
  public void run(){  
   for(int i=0; i< 3; i++){  
    System.out.println( Thread.currentThread().getName()  + " --> " + sn.getNextNum());  
   }  
  }  
    
  public void print(){  
   for(int i=0; i< 3; i++){  
    System.out.println( Thread.currentThread().getName()  + " --> " + sn.getNextNum());  
   }  
  }  
 }  
}  

下面是结果

Thread-2 --> 1  
Thread-2 --> 2  
Thread-2 --> 3  
Thread-0 --> 1  
Thread-0 --> 2  
Thread-0 --> 3  
Thread-1 --> 1  
Thread-1 --> 2  
Thread-1 --> 3  
main --> 1  
main --> 2  
main --> 3  
main --> 4  
main --> 5  
main --> 6  
main --> 7  
main --> 8  
main --> 9  

可以发现,static的ThreadLocal变量是一个与线程相关的静态变量,即一个线程内,static变量是被各个实例共同引用的,但是不同线程内,static变量是隔开的。


下面的实例能够体现Spring对有状态Bean的改造思路:
代码清单3 TopicDao:

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

由于1处的conn是成员变量,因为addTopic()方法是非线程安全的,必须在使用时创建一个新TopicDao实例(非singleton)。

下面使用ThreadLocal对conn这个非线程安全的“状态”进行改造:
代码清单4 TopicDao:

// 线程安全
package threadLocalDemo;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

public class SqlConnection {
    // 1. 使用ThreadLocal保存Connection变量
    privatestatic ThreadLocal<Connection>connThreadLocal = newThreadLocal<Connection>();
   
    publicstatic Connection getConnection() {
       // 2. 如果connThreadLocal没有本线程对应的Connection创建一个新的Connection,
       // 并将其保存到线程本地变量中。
       if (connThreadLocal.get() == null) {
           Connection conn = getConnection();
           connThreadLocal.set(conn);
           return conn;
       } else {
           return connThreadLocal.get();
           // 3. 直接返回线程本地变量
       }
    }

    public voidaddTopic() {
       // 4. 从ThreadLocal中获取线程对应的Connection
       try {
           Statement stat = getConnection().createStatement();
       } catch (SQLException e) {
           e.printStackTrace();
       }
    }
}

为何通常将ThreadLocal变量设置为static

理由:

    为了避免重复创建TSO(thread specific object,即与线程相关的变量)。
    需要注意的是:无法解决共享对象的更新问题。(引用于《阿里巴巴JAVA开发规范》)

     static定义的类变量本来是可以进行变量共享的,但是因为ThreadLocal根除了对变量的共享,所以static Thread< xxx> object无法实现类的共享和同步更新

分析

     一个ThreadLocal实例对应当前线程中的一个TSO实例。因此,如果把ThreadLocal声明为某个类的实例变量(而不是静态变量),那么每创建一个该类的实例就会导致一个新的TSO实例被创建。显然,这些被创建的TSO实例是同一个类的实例。于是,同一个线程可能会访问到同一个TSO(指类)的不同实例,这即便不会导致错误,也会导致浪费(重复创建等同的对象)!因此,一般我们将ThreadLocal使用static修饰即可。

    由于ThreadLocal是某个类的一个静态变量。因此,只要相应的类没有被垃圾回收掉,那么这个类就会持有对相应ThreadLocal实例的引用。

什么是ThreadLocal?

    java.lang.ThreadLocal类实现了线程的本地存储,我们可以用该类来创建和管理线程。

ThreadLocal的内部实现:

    ThreadLocal的内部实现包括一个类似HashMap的对象,这里称之为ThreadLocalMap。

    ThreadLocalMap的key会持有对ThreadLocal实例的弱引用(Weak Reference),value会引用TSO实例。

具体的可以参考:彻底理解ThreadLocal

ThreadLocal的使用:

《thinking in java 第四版》p690
 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!