zoukankan      html  css  js  c++  java
  • 另一种线程安全机制:在事务管理中起到巨大作用的 ThreadLocal

    ThreadLocal 是什么

    ThreadLocal 不是一个线程,而是保存线程本地化对象的容器。

    当运行于多线程环境的某个对象使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程分配一个独立的变量副本。

    所以每个线程可以独立地改变自己的副本,而不会影响其他线程所对应的变量副本。

    ThreadLocal 的接口方法

    • void set(Object value) :设置当前线程的线程局部变量值。
    • public Object get() :返回当前线程所对应的线程局部变量。
    • public void remove() :将当前线程的局部变量值删除。目的是为了减少内存的占用。
    • protected Object initialValue() :返回该线程局部变量的初始值。该方法是一个protected 的方法,显然是为了让之类覆盖而设计的。这个方法是延迟调用方法,在线程第一次调用 get() 或者 set(Object) 时才执行,并且仅执行一次。

    ThreadLocal 维护一份独立变量副本的思很简单:在 ThreadLocal 类中有一个 Map,用于存储每个线程的变量副本,Map 中元素的键为线程对象,值为对应线程的变量副本。

    ThreadLocal 实例

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

    运行结果:

    可以发现每个线程所产生的序列号都共享于同一个实例 SequenceNumber,但它们并没有互相干扰,而是各自产生独立的序列号。

    与 Thread 同步机制比较

    在同步机制中,通过对象的锁机制保证同一个时间只有一个线程访问变量。

    而 ThreadLocal 是从另一个角度来解决并发问题,ThreadLocal 为没一个线程提供独立的变量副本,从而隔离了多个线程对访问数据冲突。

    简单来说,Thread 机制是采用“以时间换空间” 的方式:访问串行化,对象共享化;而 ThreadLocal 采用了 “以空间换时间” 的方式:访问并行化,对象独享化。

    Spring 使用 ThreadLocal 解决线程安全问题

    在一般情况下,只有无状态的 Bean 才可以在多线程环境下共享。在 Spring 中,绝大部分 Bean 都可以生命为 singleton 作用域。

    正是因为 Spring 对一些 Bean 中非线程安全的 ”状态性对象“ 采用 ThreadLocal 进行封装,让他们也成为线程安全的 “状态性对象”,因此,有状态的 Bean 就能过以 singleton 的方式在多线程中正常工作。

    举个获取数据库连接的例子:

    非线程安全

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

    使用 ThreadLocal 进行改进,变成线程安全的状态

    public class TopicDao {
        // 1. 使用 ThreadLocal 保存 Connection 变量
        private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<>();
    
        public static Connection getConnection() {
    
            // 2. 如果 connThreadLocal没有,则创建一个新的 Connection,并保存到线程本地变量中
            if (connThreadLocal.get() == null) {
                Connection conn = ConnectionManager.getConnection();
                connThreadLocal.set(conn);
                return conn;
            }else {
                // 3. 直接返回线程本地变量
                return connThreadLocal.get();
            }
        }
    
        public void addTopic() {
            // 4. 从 ThreadLocal 中获取线程对应的 Connection
            Statement stat = getConnection().createStatement();
        }
    
    }
    

    这个例子本身很粗糙,将 Connection 的 ThreadLocal 直接放在 DAO 中只能做到本 DAO 的多个方法共享 Connection 时不发生线程安全问题,而无法和其他 DAO 共用一个Connection 。要做到同一个事务多DAO 共享同一个 Connection ,必须在共同的外部类使用 ThreadLocal 保存 Connection 。

    但这个例子也基本上说明了 Spring 对所有状态类线程安全化的解决思路。

  • 相关阅读:
    关于json字符串与实体之间的严格验证
    SQL Pretty Printer 一款值得你拥有的MSSQL格式化插件
    ABP增加记录EFCore 生成数据库脚本日志到新的txt文件
    Multiple types were found that match the controller named 'Auth'.
    sqlserver 交叉去重
    sqlserver分组排序取前三条数据
    C# 读取.resx资源文件写入到json文件中
    SqlServer根据经纬度排序
    .net core 简单定时程序
    使用游标,查询一张的数据往另外三张表里面添加数据
  • 原文地址:https://www.cnblogs.com/luler/p/14977897.html
Copyright © 2011-2022 走看看