`
该用户名已经存在
  • 浏览: 307208 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

谈谈ThreadLocal和解决线程安全的关系

    博客分类:
  • Java
阅读更多
这篇文章中我粗略的就我的理解谈了一下ThreadLocal。但是很多时候我们还是会认为ThreadLocal是为了解决线程安全的问题而设计的。这篇文章就我的理解再加上该文章
中很多朋友的回复阐述一下ThreadLocal和线程安全的关系。

首先我们来看一下线程安全问题产生的两个前提条件:
1. 数据共享。多个线程访问同样的数据。
2. 共享数据是可变的。多个线程对访问的共享数据作出了修改。

定义一个共享数据:
public static int a = 0;


多线程多该共享数据进行修改:
private static void plus() {
    for (int i = 0; i < 10; i++) {
        new Thread() {
            public void run() {
                a++;
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("plus:" + Thread.currentThread().getName() + ": " + a);
            }
        }.start();
    }
}

输出结果:
plus:Thread-5: 5
plus:Thread-2: 5
plus:Thread-6: 5
plus:Thread-9: 5
plus:Thread-1: 6
plus:Thread-0: 8
plus:Thread-4: 8
plus:Thread-3: 10
plus:Thread-7: 10
plus:Thread-8: 10

很明显,在第一次输出a的值的时候,a的值就已经被其他线程修改到5了,显然线程不安全。

利用synchronized关键字将修改a值的地方和输出的地方上锁。让这段代码在某一个时间段内始终只有一个
线程在执行:
private static void plus() {
    for (int i = 0; i < 10; i++) {
        new Thread() {
            public void run() {
                synchronized (JavaVariable.class) {
                    a++;
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("plus:" + Thread.currentThread().getName() + ": " + a);
                }
            }
        }.start();
    }
}

输出结果:
plus:Thread-2: 1
plus:Thread-6: 2
plus:Thread-7: 3
plus:Thread-3: 4
plus:Thread-8: 5
plus:Thread-4: 6
plus:Thread-0: 7
plus:Thread-9: 8
plus:Thread-5: 9
plus:Thread-1: 10

结果正确。

那么,如果ThreadLocal是为了解决线程安全设计的,请同样用ThreadLocal解决上面的问题。即,我需要多个线程共同修改一个值,但是要保持这个值递增,就是说后面的线程不能在前一个线程还没有输出就又去修改。

当然可以尝试一下,首先定义一个ThreadLocal。
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
    protected Integer initialValue() {
        return 0;
    }
};

插一句题外话:为什么threadLocal要被声明成静态的?不声明成静态的行不行?
我只是有一点儿想法,还不是很明白,所以暂且先不讨论这个话题,大家也可以思考一下,有什么高见感谢回复到文章后面。
接着上面的话题,也许可以这样:
private static void plus() throws Exception {
    for (int i = 0; i < 10; i++) {
        new Thread() {
            public void run() {
                //1
                a = threadLocal.get();
                a++;
                
                //2
                threadLocal.set(a);
                System.out.println("plus:" + Thread.currentThread().getName() + ": " + threadLocal.get());
            }
        }.start();
    }
}

我都不想运行结果了,全部会输出1。很明显,在代码“1”的时候,每一个线程都会将threadLocal的初始值0赋值给共享变量a,因为每一个线程从threadLocal.get()拿到的值都是自己threadLocal保存的。所以,就没有所以了。
所以对于共享变量a来讲,每个线程都会首先将自己threadLocal里面的初始值0赋值给a,然后将共享变量a+1,然后将a+1的值设置到自己的ThreadLocalMap中,其他线程就访问不到了。下一个线程来的时候又会将自己threadLocal里面的初始值0赋值给a,然后将 a+1,然后... 如此周而复始。a只是被在0和1之间改来改去,最终放到每一个线程的threadLocal里面的a+1的值就不再共享。对于a这个共享变量来讲,如果在for循环创建的某线程A即将执行代码“2”之前,被其他for循环以外的某个线程改了一把。那么存到线程A的ThreadLocalMap中的值就是被改过的值了。

综上所述:
Java的ThreadLocal不是设计用来解决多线程安全问题的,事实证明也解决不了,共享变量a还是会被随意更改。ThreadLocal无能为力。所以,一般用ThreadLocal都不会将一个共享变量放到线程的ThreadLocal中。一般来讲,存放到ThreadLocal中的变量都是当前线程
本身就独一无二的一个变量。其他线程本身就不能访问,存到ThreadLocal中只是为了方便在程序中同一个线程之间传递这个变量。
所以,ThreadLocal和解决线程安全没有关系。
例如Hibernate的代码:
 public static final ThreadLocal<session> sessions =    
                                            new ThreadLocal<session>(); 
 public static Session currentSession() throws HibernateException {   
    Session s = sessions.get();   
    //如果从当前线程变量中获取的session为空,直接open一个session放到当前线程变量中。
    //值得注意的是,这里的session引用s是在方法体内声明的,属于局部变量,其他线程根本访问不到。
    //就是说,这里的session s本身就不是一个共享对象,本身就是当前线程独一无二的存在。
    //下一个线程来的时候,从sessions.get()拿出来的session s又是下一个线程的线程变量中的值。如果为空,同样创建。
    //所以,session在线程变量内部还是外部都是没有和任何线程共享的。
    if(s == null) {   
         s = sessionFactory.openSession();   
         sessions.set(s);   
    }   
         


补充一个问题:
当使用线程池的时候,由于ThreadLocal的设计原理是将一个ThreadLocalMap的引用作为Thread的一个属性,利用当前ThreadLocal作为key,保存的变量值作为value保存在当前线程的ThreadLocalMap中的。所以ThreadLocalMap是伴随的Thread本身的存在而存在的,只要Thread不被回收,ThreadLocalMap就存在。因此,对于线程池来讲,重复利用一个Thread就等于在重复利用Thread的ThreadLocalMap,所以ThreadLocalMap里面保存的数据可能会被多次使用。
例子:
private static void plus() {
    Executor executor = Executors.newFixedThreadPool(2);
    for (int i = 0; i < 10; i++) {
        executor.execute(new Runnable() {
            public void run() {
                a = threadLocal.get();
                threadLocal.set(++a);
                System.out.println("plus:" + Thread.currentThread().getName() + ": " + a);

            }
        });
    }
}

输出结果:
plus:pool-1-thread-1: 1
plus:pool-1-thread-1: 2
plus:pool-1-thread-1: 3
plus:pool-1-thread-1: 4
plus:pool-1-thread-1: 5
plus:pool-1-thread-1: 6
plus:pool-1-thread-1: 7
plus:pool-1-thread-1: 8
plus:pool-1-thread-1: 9
plus:pool-1-thread-2: 1

可以看出,对于线程池中的两个线程来讲,线程1一直在被重复使用,虽然每次都是从ThreadLocal获取值,但是都是同一个ThreadLocalMap,所以值是递增的。对于线程2来讲,和线程1不是一个线程,不是一个ThreadLocalMap,所以值依然是1。

线程重复利用,又不想重复利用其ThreadLocalMap中的值怎么办呢?
我的理解是:
一般情况下,我们自己用的话ThreadLocal里面都存放的是无状态的对象,只是便于在同一个线程中参数传递,所以个人认为即使重复利用也没有关系。但是如果ThreadLocal里面存放的是有状态的对象的话,在线程使用结束后直接将当前线程的ThreadLocalMap里的值设为初始值就可以了,或者直接remove掉。
比如 Struts2里面的ActionContext:
public class ActionContext implements Serializable {
    static ThreadLocal actionContext = new ThreadLocal();
    ....
    public static void setContext(ActionContext context) {
        actionContext.set(context);
    }
}

在struts2的org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter类中,就会无论如何在请求结束后清除当前线程的ThreadLocalMap中的值:
} finally {
            prepare.cleanupRequest(request);
        }

public void cleanupRequest(HttpServletRequest request) {
    ActionContext.setContext(null);
}









1
4
分享到:
评论
9 楼 fnet 2016-04-02  
楼主说的没错,如果当前线程在线程池中会被重复利用,那么threadlocal中的数据在某种情况下也会被重复使用。

在某些情况下会产生线程安全问题。
8 楼 851228082 2016-01-06  
public static final ThreadLocal<session> sessions =     
                                           new ThreadLocal<session>();  
public static Session currentSession() throws HibernateException {    
   Session s = sessions.get();  

此处的session是sql session吧,此处是否安全与局部变量没有关系,与session是否是有状态的有关。
7 楼 851228082 2016-01-06  
楼主已经讲得很明白了,threadlocal是线程的本地变量,在线程池情况下,由于线程会重复利用,所以threadlocal也会被重复利用,如果变量无状态就无所谓,如果变量是有状态的,那么就会出现问题。

线程池情况下,我就遇到问题了,spring 多数据源,我将数据源放在threadlocal中,由于thread的重复利用,导致查询了错误数据源。
6 楼 youjianbo_han_87 2014-03-21  
ThreadLocal模式主要用于解决同一线程在不同开发层次中数据共享问题,而不是用于不同开发层次数据传递问题,比如spring的数据库连接传递性的实现。
5 楼 liubey 2014-03-12  
上个回复说的不严谨。
从未听说过threadlocal还有线程共享这个概念,threadlocal里面的数据本身就是每个线程一份,也就是线程私有的,哪来的线程共享的概念?我好像真有点晕了哈哈 LZ能说明一下吗
4 楼 liubey 2014-03-12  
该用户名已经存在 写道
liubey 写道
threadlocal到底是线程共享还是线程安全的?我都被楼主弄懵了


不一定。如果ThreadLocal里面保存一个线程共享的变量,对于该变量来讲是线程不安全的。
如果ThreadLocal保存的本身就是一个私有的,不共享的变量的话,当然是线程安全的。

该用户名已经存在 写道
liubey 写道
threadlocal到底是线程共享还是线程安全的?我都被楼主弄懵了


不一定。如果ThreadLocal里面保存一个线程共享的变量,对于该变量来讲是线程不安全的。
如果ThreadLocal保存的本身就是一个私有的,不共享的变量的话,当然是线程安全的。

从未听说过threadlocal还有线程共享这个概念,threadlocal本身就是每个线程一份,也就是线程私有的,哪来的线程共享的概念?我好像真有点晕了哈哈 LZ能说明一下吗
3 楼 该用户名已经存在 2014-03-11  
liubey 写道
threadlocal到底是线程共享还是线程安全的?我都被楼主弄懵了


不一定。如果ThreadLocal里面保存一个线程共享的变量,对于该变量来讲是线程不安全的。
如果ThreadLocal保存的本身就是一个私有的,不共享的变量的话,当然是线程安全的。
2 楼 liubey 2014-03-11  
threadlocal到底是线程共享还是线程安全的?我都被楼主弄懵了
1 楼 timer_yin 2014-03-11  
长知识了

相关推荐

    java ThreadLocal多线程专属的变量源码

    java ThreadLocal多线程专属的变量源码java ThreadLocal多线程专属的变量源码java ThreadLocal多线程专属的变量源码java ThreadLocal多线程专属的变量源码java ThreadLocal多线程专属的变量源码java ThreadLocal多...

    ThreadLocal:如何优雅的解决SimpleDateFormat多线程安全问题

    目录SimpleDateFormat诡异bug复现SimpleDateFormat诡异bug字符串日期转Date日期(parse)Date日期转String类型(format)SimpleDateFormat出现...注意事项使用ThreadLocal解决SimpleDateFormat线程安全问题总结...

    ThreadLocal

    - 不是线程安全的:尽管ThreadLocal提供了线程隔离,但它本身并不保证线程安全性,如果在`set`和`get`操作之间有其他线程修改了ThreadLocal实例,仍需进行同步控制。 在使用ThreadLocal时,理解其工作原理和限制是...

    Java ThreadLocal 线程安全问题解决方案

    Java中的ThreadLocal是解决线程安全问题的一个重要工具,它提供了一种在多线程环境下为每个线程维护独立变量副本的方法,从而避免了共享状态带来的竞态条件和线程安全问题。 线程安全问题通常由全局变量和静态变量...

    C# 高效线程安全,解决多线程写txt日志类.zip

    总的来说,这个"C# 高效线程安全,解决多线程写txt日志类.zip"提供的方案对于多线程环境下的日志管理有着显著的价值,通过合理的同步机制和优化策略,实现了日志写入的安全性和高效性。开发者可以通过学习和使用这个...

    Java非线程安全类变线程安全类.pdf

    Java 非线程安全类变线程安全类 Java 中的非线程安全类是指有状态的类,即有属性的类,这些类在多线程...Java 中的非线程安全类可以通过使用 ThreadLocal 或 synchronized 关键字来实现线程安全,避免线程安全问题。

    ThreadLocal应用示例及理解

    **线程局部变量(ThreadLocal)是Java编程中一个非常重要的工具类,它在多线程环境下提供了线程安全的数据存储。ThreadLocal并不是一个变量,而是一个类,它为每个线程都创建了一个独立的变量副本,使得每个线程都...

    servlet与Struts action线程安全问题分析

    Servlet和Struts Action是两种常见的Java Web开发组件,它们在多线程环境下运行时可能存在线程安全问题。线程安全是指在多线程环境中,一个类或者方法能够正确处理多个线程的并发访问,保证数据的一致性和完整性。 ...

    java线程安全测试

    以下是一些常见的线程安全问题和解决策略: 1. 数据竞争:当两个或更多线程同时修改一个共享变量时,可能会导致数据不一致。解决方法是使用synchronized或java.util.concurrent包中的工具类,如AtomicInteger、...

    day18 10.使用ThreadLocal来解决问题

    在Java编程中,ThreadLocal是线程局部变量的类,它为每个线程提供了一个独立的变量副本,...总的来说,ThreadLocal是Java中解决线程间数据隔离问题的强大工具,但在使用时需要注意内存管理和线程安全,避免潜在的问题。

    ThreadLocal和事务

    在Java编程领域,ThreadLocal和事务管理是两...ThreadLocal提供了线程安全的变量管理,c3p0优化了数据库连接的使用,而事务则保证了数据操作的完整性。这样的设计模式对于大型、并发性强的Web系统有着重要的实践意义。

    高并发之-SimpleDateFormat类的线程安全问题和解决方案.docx

    SimpleDateFormat类的线程安全问题和解决方案 SimpleDateFormat类的线程安全问题 SimpleDateFormat类是Java提供的日期时间转化类,用于将日期和时间类型的数据进行解析和格式化。在Java开发中,SimpleDateFormat类...

    TestNG多线程安全吗?ThreadLocal:有我还能不安全?

    目录一、背景介绍二、TestNG多线程详解2.1 TestNG多线程实现2.2 TestNG多线程效果演示三、ThreadLocal3.1 ThreadLocal概念3.2 具体实现 ...所以就使用到ThreadLocal这个类去保证线程安全。 二、TestN

    事务的封装和Threadlocal实例

    总的来说,结合JDBC的事务管理和ThreadLocal,我们可以在多线程环境中更好地实现数据库操作,确保数据的一致性,并提高代码的可复用性和安全性。通过使用ThreadLocal,我们可以创建线程安全的变量,使得每个线程都能...

    局部变量线程安全测试

    测试可能包括对局部变量的读写操作,以及涉及到同步机制如synchronized关键字,volatile修饰符,或者是使用ThreadLocal等技术来确保线程安全。 在标签中,“局部变量”、“线程”、“安全”、“测试”和“源码”是...

    java事务 - threadlocal

    Java事务和ThreadLocal是两种在Java编程中至关重要的概念,它们分别用于处理多线程环境下的数据一致性问题和提供线程局部变量。 首先,我们来深入理解Java事务。在数据库操作中,事务是一系列操作的集合,这些操作...

    设计模式及ThreadLocal资料

    ThreadLocal由于提供了线程局部副本,因此在一定程度上解决了线程安全问题。 总结一下,这份资料涵盖了设计模式中的单例模式、工厂模式和代理模式,以及Java中的ThreadLocal特性。理解并熟练应用这些概念和技术,...

    ThreadLocal原理及在多层架构中的应用

    ThreadLocal不是一种数据结构,而是一种解决线程间共享数据的方式,它提供了线程安全的局部变量。** ### 1. ThreadLocal的原理 ThreadLocal的工作原理主要基于以下几点: - **内部类ThreadLocalMap**:...

    Java多线程 - (一) 最简单的线程安全问题

    5. **ThreadLocal**:每个线程都有自己的副本,不会产生线程安全问题,但要注意内存泄漏。 了解了这些基本机制后,我们可以通过`jconsole`、`jvisualvm`等工具进行线程监控,查看线程状态、死锁检测等,辅助排查和...

    ThreadLocal中内存泄漏和数据丢失问题的问题浅析及解决方案.docx

    ThreadLocal 中内存泄漏和数据丢失问题的问题浅析及解决方案 ThreadLocal 是 Java 中的一种线程本地存储机制,它可以解决线程之间的数据传递问题。然而,在使用 ThreadLocal 时,可能会出现内存泄漏和数据丢失问题...

Global site tag (gtag.js) - Google Analytics