首先来看一下线程安全问题产生的两个前提条件:
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; } }; 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里面保存的数据可能会被多次使用。
实例:
import java.util.concurrent.Executor; import java.util.concurrent.Executors; public class ThreadLocalTest { public static void main(String[] args) { plus(); } private static void plus() { Executor executor = Executors.newFixedThreadPool(2); for (int i = 0; i < 10; i++) { executor.execute(new Runnable() { public void run() { int a = threadLocal.get(); threadLocal.set(++a); System.out.println("plus:" + Thread.currentThread().getName() + ": " + a); } }); } } private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() { protected Integer initialValue() { return 0; } }; }
运行结果:
plus:pool-1-thread-1: 1 plus:pool-1-thread-2: 1 plus:pool-1-thread-1: 2 plus:pool-1-thread-2: 2 plus:pool-1-thread-1: 3 plus:pool-1-thread-2: 3 plus:pool-1-thread-1: 4 plus:pool-1-thread-2: 4 plus:pool-1-thread-1: 5 plus:pool-1-thread-2: 5
可以看出,对于线程池中的两个线程来讲,虽然每次都是从ThreadLocal获取值,但是都是同一个ThreadLocalMap,所以值是递增的。
线程重复利用,又不想重复利用其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); }
文章来源:http://zhangbo-peipei-163-com.iteye.com/blog/2029216
另一篇关于ThreadLocal的文章:http://bijian1013.iteye.com/blog/1871752
相关推荐
Java 非线程安全类变线程安全类 Java 中的非线程安全类是指有状态的类,即有属性的类,这些类在多线程...Java 中的非线程安全类可以通过使用 ThreadLocal 或 synchronized 关键字来实现线程安全,避免线程安全问题。
**线程局部变量(ThreadLocal)是Java编程中一个非常重要的工具类,它在多线程环境下提供了线程安全的数据存储。ThreadLocal并不是一个变量,而是一个类,它为每个线程都创建了一个独立的变量副本,使得每个线程都...
Servlet和Struts Action是两种常见的Java Web开发组件,它们在多线程环境下运行时可能存在线程安全问题。线程安全是指在多线程环境中,一个类或者方法能够正确处理多个线程的并发访问,保证数据的一致性和完整性。 ...
目录SimpleDateFormat诡异bug复现SimpleDateFormat诡异bug字符串日期转Date日期(parse)Date日期转String类型(format)SimpleDateFormat出现...注意事项使用ThreadLocal解决SimpleDateFormat线程安全问题总结...
构建线程安全的应用程序是并发编程中的一个重要议题,涉及到多个线程共享同一资源时如何保持数据的一致性与完整性。 首先,线程安全性关注的是对象在操作过程中的状态完整性。在单线程环境下,对象的操作不会被打断...
"C# 高效线程安全,解决多线程写txt日志类.zip" 提供了一个专门用于多线程环境下写入txt日志文件的解决方案,确保了在并发写入时的数据一致性与程序稳定性。 首先,我们要理解什么是线程安全。线程安全是指当多个...
#### 三、使用ThreadLocal实现线程安全 假设我们有一个非线程安全的变量需要转换为线程安全的变量。通常的做法是使用同步机制将对象封装到同步块中,但这可能会导致性能瓶颈。另一种更高效的方式是使用ThreadLocal...
在Java中,线程安全问题通常与并发、内存模型和可见性有关。Java内存模型(JMM)定义了如何在多线程环境下共享数据的规则,确保线程之间的正确交互。 线程安全可以分为三种类型: 1. 不安全:当多个线程访问共享...
测试可能包括对局部变量的读写操作,以及涉及到同步机制如synchronized关键字,volatile修饰符,或者是使用ThreadLocal等技术来确保线程安全。 在标签中,“局部变量”、“线程”、“安全”、“测试”和“源码”是...
在本项目“Java多线程与线程安全实践-基于Http协议的断点续传”中,我们将深入探讨如何利用Java的多线程机制实现HTTP协议下的断点续传功能,这对于大文件下载或上传的场景尤为实用。 断点续传是一种允许用户在中断...
Java线程安全是多线程编程中的一个关键概念,它涉及到在并发环境下如何正确地管理共享资源,确保程序的正确性和一致性。以下是对Java线程安全的深入总结: ### 一、线程安全的定义 线程安全是指当多个线程访问同一...
Java中的ThreadLocal是解决线程安全问题的一个重要工具,它提供了一种在多线程环境下为每个线程维护独立变量副本的方法,从而避免了共享状态带来的竞态条件和线程安全问题。 线程安全问题通常由全局变量和静态变量...
- ThreadLocal并不是解决所有线程安全问题的万能药,它主要用于隔离线程间的变量状态,而非同步控制。 - 不要将ThreadLocal用作全局变量,因为它们只在创建它们的线程内有效,无法跨线程共享。 - 谨慎处理生命...
ThreadLocal是Java编程中一种非常特殊的变量类型,它主要用于在多线程环境下为每个线程提供独立的变量副本,从而避免了线程间的数据共享和...因此,在使用ThreadLocal时,需要充分考虑其生命周期管理和线程安全问题。
在C#编程中,线程安全是一个至关重要的概念,特别是在多线程环境下,它涉及到多个线程同时访问共享资源时的正确性和一致性。本项目提供了一系列的源代码示例,帮助开发者理解和实现线程安全。 首先,理解线程安全的...
需要注意的是,ThreadLocal不是线程安全的,它只是保证了线程内部的隔离性,但不负责线程间的同步。 当Java事务与ThreadLocal结合使用时,可以在不同的线程中维护各自的事务状态,比如在Spring框架中,每个线程的...
#### 一、Struts1与线程安全问题 在Struts1中,每个`Action`类实例是被多个请求重用的,这使得它在多线程环境下存在潜在的线程安全问题。当多个线程同时访问一个`Action`实例时,可能会因为共享状态而导致数据不...
线程安全是多线程编程中的一个关键概念,它涉及到一个对象在被多个线程并发访问时,是否能保持其正确性,即不会出现数据不一致或异常。现在我们来详细讨论`Action`以及其线程安全性。 首先,让我们区分两种可能的`...