用ThreadLocal类实现线程安全的正确姿势

  大家通常知道,ThreadLocal类可以帮助我们实现线程的安全性,这个类能使线程中的某个值与保存值的对象关联起来。ThreadLocal提供了get与set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值。从概念上看,我们把ThreadLocal<T>理解成一个包含了Map<Thread,T>的对象,其中Map的key用来标识不同的线程,而Map的value存放了特定该线程的某个值。但是ThreadLocal的实现并非如此,我们以这样的理解方式去使用ThreadLocal也并不能实现真正的线程安全。

  下面我们举一个例子进行说明,Number是拥有一个int型成员变量的类:

public class Number {

    private int num;

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    @Override
    public String toString() {
        return "Number [num=" + num + "]";
    }

}

  NotSafeThread是一个实现了Runable接口的类,其中我们创建了一个ThreadLocal<Number>类型的变量value,用来存放不同线程的num值,接着我们用线程池的方式启动了5个线程,我们希望使用ThreadLocal类为5个不同的线程都存放一个Number类型的副本,根除对变量的共享,并且在调用ThreadLocal类的get()方法时,返回与线程关联的Number对象,而这些Number对象我们希望它们都能跟踪自己的计数值:

public class NotSafeThread implements Runnable {

    public static Number number = new Number();

    public static int i = 0;

    public void run() {
        //每个线程计数加一
        number.setNum(i++);
     //将其存储到ThreadLocal中
        value.set(number);
        //输出num值
        System.out.println(value.get().getNum());
    }

    public static ThreadLocal<Number> value = new ThreadLocal<Number>() {
    };

    public static void main(String[] args) {
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            newCachedThreadPool.execute(new NotSafeThread());
        }
    }

}

  启动程序:输出结果

0
1
2
3
4

  看起来一切正常,每个线程好像都有自己关于Number的存储空间,但是我们简单的在输出前加一个延时:

public class NotSafeThread implements Runnable {

    public static Number number = new Number();

    public static int i = 0;

    public void run() {
        //每个线程计数加一
        number.setNum(i++);
        //将其存储到ThreadLocal中
        value.set(number);
        //延时2秒
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
        }
        //输出num值
        System.out.println(value.get().getNum());
    }

    public static ThreadLocal<Number> value = new ThreadLocal<Number>() {
    };

    public static void main(String[] args) {
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            newCachedThreadPool.execute(new NotSafeThread());
        }
    }

}

  运行程序,输出:

4
4
4
4
4

  为什么每个线程都输出4?难道他们没有独自保存自己的Number副本吗?为什么其他线程还是能够修改这个值?我们看一下ThreadLocal的源码:

    public void set(Object obj)
    {
        Thread thread = Thread.currentThread();//获取当前线程
        ThreadLocalMap threadlocalmap = getMap(thread);
        if(threadlocalmap != null)
            threadlocalmap.set(this, obj);
        else
            createMap(thread, obj);
    }

  其中getMap方法:

    ThreadLocal.ThreadLocalMap getMap(Thread thread)
    {
        return thread.inheritableThreadLocals;//返回的是thread的成员变量
    }

  可以看到,这些特定于线程的值是保存在当前的Thread对象中,并非保存在ThreadLocal对象中。并且我们发现Thread对象中保存的是Object对象的一个引用,这样的话,当有其他线程对这个引用指向的对象做修改时,当前线程Thread对象中保存的值也会发生变化。这也就是为什么上面的程序为什么会输出一样的结果:5个线程中保存的是同一Number对象的引用,在线程睡眠2s的时候,其他线程将num变量进行了修改,因此它们最终输出的结果是相同的。

  那么,ThreadLocal的“为每个使用该变量的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值。”这句话中的“独立的副本”,也就是我们理解的“线程本地存储”只能是每个线程所独有的对象并且不与其他线程进行共享,大概是这样的情况:

    public static ThreadLocal<Number> value = new ThreadLocal<Number>() {
        public Number initialValue(){//为每个线程保存的值进行初始化操作
            return new Number();
        }
    };

  或者

    public void run() {
        value.set(new Number());
    }

  好吧...这个时候估计你会说:那这个ThreadLocal有什么用嘛,每个线程都自己new一个对象使用,只有它自己使用这个对象而不进行共享,那么程序肯定是线程安全的咯。这样看起来我不使用ThreadLocal,在需要用某个对象的时候,直接new一个给本线程使用不就好咯。

  确实,ThreadLocal的使用不是为了能让多个线程共同使用某一对象,而是我有一个线程A,其中我需要用到某个对象o,这个对象o在这个线程A之内会被多处调用,而我不希望将这个对象o当作参数在多个方法之间传递,于是,我将这个对象o放到TheadLocal中,这样,在这个线程A之内的任何地方,只要线程A之中的方法不修改这个对象o,我都能取到同样的这个变量o。

  再举一个在实际中应用的例子,例如,我们有一个银行的BankDAO类和一个个人账户的PeopleDAO类,现在需要个人向银行进行转账,在PeopleDAO类中有一个账户减少的方法,BankDAO类中有一个账户增加的方法,那么这两个方法在调用的时候必须使用同一个Connection数据库连接对象,如果他们使用两个Connection对象,则会开启两段事务,可能出现个人账户减少而银行账户未增加的现象。使用同一个Connection对象的话,在应用程序中可能会设置为一个全局的数据库连接对象,从而避免在调用每个方法时都传递一个Connection对象。问题是当我们把Connection对象设置为全局变量时,你不能保证是否有其他线程会将这个Connection对象关闭,这样就会出现线程安全问题。解决办法就是在进行转账操作这个线程中,使用ThreadLocal中获取Connection对象,这样,在调用个人账户减少和银行账户增加的线程中,就能从ThreadLocal中取到同一个Connection对象,并且这个Connection对象为转账操作这个线程独有,不会被其他线程影响,保证了线程安全性。

  代码如下:

public class ConnectionHolder {

    public static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
    };

    public static Connection getConnection(){
        Connection connection = connectionHolder.get();
        if(null == connection){
            connection = DriverManager.getConnection(DB_URL);
            connectionHolder.set(connection);
        }
        return connection;
    }

}

  在框架中,我们需要将一个事务上下文(Transaction  Context)与某个执行中的线程关联起来。通过将事务上下文保存在静态的ThreaLocal对象中(这个上下文肯定是不与其他线程共享的),可以很容易地实现这个功能:当框架代码需要判断当前运行的是哪一个事务时,只需从这个ThreadLocal对象中读取事务上下文。这种机制很方便,因为它避免了在调用每个方法时都需要传递执行上下文信息,然而这也将使用该机制的代码与框架耦合在一起。

  

时间: 2024-10-03 13:18:58

用ThreadLocal类实现线程安全的正确姿势的相关文章

Android PermissionUtils:运行时权限工具类及申请权限的正确姿势

Android PermissionUtils:运行时权限工具类及申请权限的正确姿势 ifadai 关注 2017.06.16 16:22* 字数 318 阅读 3637评论 1喜欢 6 PermissionUtil 经常写Android运行时权限申请代码,每次都是复制过来之后,改一下权限字符串就用,把代码搞得乱糟糟的,于是便有了封装工具类的想法,话不多说,先看怎么用: 工具类及Demo:github 简洁版申请权限 申请一个权限: PermissionUtils.checkAndRequest

多线程06_张孝祥-ThreadLocal类及应用技巧

使用ThreadLocal类实现线程范围内的共享数据: 确保每个线程都有自己私有的变量和取到自己的变量.下面用ThreadLocal类实现,替代上节课中的map用法 代码: package org.yla.thread; import java.util.Random; /** * 使用ThreadLocal类实现线程范围内的私有数据 * 替代上节课map的作用 * @author huo_chai_gun * 2014年12月18日下午3:25:35 */ public class Threa

Java线程与并发库高级应用-线程范围内共享数据ThreadLocal类

1.线程范围内共享变量 1.1 前奏: 使用一个Map来实现线程范围内共享变量 public class ThreadScopeShareData { static Map<Thread, Integer> dataMap = new HashMap<Thread, Integer>(); public static void main(String[] args) { for (int i = 0; i < 2; i++) { new Thread(new Runnable

【ThreadLocal】深入JDK源码之ThreadLocal类

学习JDK中的类,首先看下JDK API对此类的描述,描述如下: 该类提供了线程局部 (thread-local) 变量.这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本.ThreadLocal其实就是一个工具类,用来操作线程局部变量,ThreadLocal 实例通常是类中的 private static 字段.它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联. 例如,以下类生成对每个线程

ThreadLocal类的用法与源码解析

首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的.各个线程中访问的是不同的对象. 另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本.通过ThreadLocal.set()将这个新创建

ThreadLocal类的理解

首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的.各个线程中访问的是不同的对象. 另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本.通过ThreadLocal.set()将这个新创建

弄明白ThreadLocal类

1.ThreadLocal类的由来 因为有问题,人类就会想法设法的创造一些东西出来解决问题,嗯,这句话同意吧. 假如目前有这么一个问题:有个家庭,三个孩子都想看妈妈买的一本童话书,但是只有一本书,该如何是好? 方法一:家里没钱买第二本了,那就排队看,谁跑得快来到妈妈面前的就先看.后面来晚的,候着等着.等前面的看完再到你.于是 以时间换空间的synchronized 类出现了. 方法二:多大的事儿,你们爸爸有钱,随便任性.立马再买两本一模一样的,人手一本.于是以空间换时间的ThreadLocal类

ThreadLocal类详解

众所周知,ThreadLocal对象可以每一个线程保存一份值,可以避免因线程间共享数据带来的问题. 其实现的原理,大致如下,具体的可以参考JDK里的源码. Thread类中,有一个threadLocals字段,它是ThreadLocalMap类型(ThreadLocal里的一个静态内部类).该字段存放当前线程下,所有与ThreadLocal相关的值.该对象是一个Map,key为ThreadLocal对象,value为所存放的值. 在ThreadLocal类里,有两个重要的方法:set()和get

ThreadLocal类的实现用法

ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量).也许把它命名为ThreadLocalVar更加合适.线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突. 从线程的角度看,每个线程都保持一个对其线程局部变量副本