ThreadLocal使用

作者: 逆水寻洲 | 来源:发表于2019-05-23 17:08 被阅读10次

ThreadLocal是什么

ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。它是一个数据结构,有点像HashMap,可以保存"key : value"键值对,但是一个ThreadLocal只能保存一个,并且各个线程的数据互不干扰。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

内容参考:ThreadLocal

ThreadLocal作用

ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

这句话从字面上看起来很容易理解,但是真正理解并不是那么容易。
我们还是先来看一个例子:

   class ConnectionManager {
        private static Connection connect = null;
        public static Connection openConnection() {
            if(connect == null){
                connect = DriverManager.getConnection();
            }
            return connect;
        }
        public static void closeConnection() {
            if(connect!=null)
                connect.close();
        }
    }

假设有这样一个数据库链接管理类,这段代码在单线程中使用是没有任何问题的,但是如果在多线程中使用呢?很显然,在多线程中使用会存在线程安全问题:
第一,这里面的2个方法都没有进行同步,很可能在openConnection方法中会多次创建connect。
第二,由于connect是共享变量,那么必然在调用connect的地方需要使用到同步来保障线程安全,因为很可能一个线程在使用connect进行数据库操作,而另外一个线程调用closeConnection关闭链接。

所以出于线程安全的考虑,必须将这段代码的两个方法进行同步处理,并且在调用connect的地方需要进行同步处理。
这样将会大大影响程序执行效率,因为一个线程在使用connect进行数据库操作的时候,其他线程只有等待。

那么大家来仔细分析一下这个问题,这地方到底需不需要将connect变量进行共享?事实上,是不需要的。假如每个线程中都有一个connect变量,各个线程之间对connect变量的访问实际上是没有依赖关系的,即一个线程不需要关心其他线程是否对这个connect进行了修改的。

到这里,可能会有朋友想到,既然不需要在线程之间共享这个变量,可以直接这样处理,在每个需要使用数据库连接的方法中具体使用时才创建数据库链接,然后在方法调用完毕再释放这个连接。比如下面这样:

 class ConnectionManager {
        private Connection connect = null;
        public Connection openConnection() {
            if(connect == null){
                connect = DriverManager.getConnection();
            }
            return connect;
        }
        public void closeConnection() {
            if(connect!=null)
                connect.close();
        }
    }

    class Dao{
        public void insert() {
            ConnectionManager connectionManager = new ConnectionManager();
            Connection connection = connectionManager.openConnection();
            //使用connection进行操作  
            connectionManager.closeConnection();
        }
    }

这样处理确实也没有任何问题,因为每次都是在方法内部创建的连接,那么线程之间自然不存在线程安全问题。但是这样会有一个致命的影响:导致服务器压力非常大,并且严重影响程序执行性能。由于在方法中需要频繁地开启和关闭数据库连接,这样不仅严重影响程序执行效率,还可能导致服务器压力巨大。

那么这种情况下使用ThreadLocal是再适合不过的了,因为ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。

但是要注意,虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。

ThreadLocal的接口方法

ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:

  • void set(Object value)
    设置当前线程的线程局部变量的值。
  • public Object get()
    该方法返回当前线程所对应的线程局部变量。
  • public void remove()
    将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
  • protected Object initialValue()
    返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

案例:创建三个线程,每个线程生成自己独立序列号。
代码:

public class ThreadLocalDemo extends Thread{

    private ResultData data;

    public ThreadLocalDemo(ResultData data) {
        this.data = data;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + "i---" + i + "--num:" + data.getNum());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ResultData data = new ResultData();
        ThreadLocalDemo threadLocaDemo1 = new ThreadLocalDemo(data);
        ThreadLocalDemo threadLocaDemo2 = new ThreadLocalDemo(data);
        ThreadLocalDemo threadLocaDemo3 = new ThreadLocalDemo(data);
        threadLocaDemo1.start();
        threadLocaDemo2.start();
        threadLocaDemo3.start();
        Thread.sleep(300);
        System.out.println(ResultData.count);
    }

}

class ResultData{
    // 生成序列号共享变量
    public static Integer count=0;
    private static ThreadLocal<Integer> threadLocal=new ThreadLocal<Integer>(){
        protected Integer initialValue() {
            return 0;
        }
    };
    public Integer getNum() {
        int count = threadLocal.get() + 1;
        threadLocal.set(count);
        return count;
    }
}

执行结果:


代码执行结果.png

相关文章

网友评论

    本文标题:ThreadLocal使用

    本文链接:https://www.haomeiwen.com/subject/wmqbzqtx.html