热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

ThreadLocal深入理解修订版

本文是传智博客多线程视频的学习笔记。原版本见http:blog.csdn.netdlf123321articledetails42531979ThreadLocal是一个和线程安全相关的类

本文是传智博客多线程视频的学习笔记。

原版本见

http://blog.csdn.net/dlf123321/article/details/42531979

ThreadLocal是一个和线程安全相关的类。

它能干什么?
能保证在一个线程内,某个变量的全局共享。

说的很模糊,咱们看一个图



线程1里面的数据,应该在线程1范围内的模块a,b,c都能访问。
线程2里面的数据,应该在线程3范围内的模块a,b,c都能访问。
且线程1,2之间数据不会混淆。
那它有什么用呢?
举个例子,银行的转账包含两步,存款和取款,时候如果在存款取款中间出了问题,就得回滚;如果一切正常等整个交易完成了再commit,而调用commit的对象是Connection。那你说,如果多个线程共用一个Connection会发生什么问题?


一个非线程安全的例子

在我们讲述ThreadLocal之前,我们先看一个例子。

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadScopeDataShare {
static private int data = 0;

public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();
for (int i = 0; i <2; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
int data2= new Random().nextInt(100);
System.out.println(Thread.currentThread().getName()+" put "+data2);
data=data2;
try {
Thread.sleep(1000); //为什么要睡1秒 大家懂吗?
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
new A().get();
new B().get();

}
});
}
threadPool.shutdown();
}

public static int getData() {
return data;
}

}

class A {
public int get() {
int data = ThreadScopeDataShare.getData();
System.out
.println("a "+Thread.currentThread().getName() + " getdata " + data);
return data;
}
}

class B {
public int get() {
int data = ThreadScopeDataShare.getData();
System.out
.println("b "+Thread.currentThread().getName() + " getdata " + data);
return data;
}
}


在我们设想中,应该是线程1放的数据,在线程1中,模块A与模块取得的数据应该是一致的。同理,线程2放的数据,与工作再线程2下的模块A模板取得的数据也应该是一致的。
可是上面的代码的运行结果却是:
pool-1-thread-1 put 90
pool-1-thread-2 put 78
a  pool-1-thread-2 getdata 78
b  pool-1-thread-2 getdata 78
a  pool-1-thread-1 getdata 78
b  pool-1-thread-1 getdata 78

改进版

我们新建一个map,key是当前线程,value是我们要保存的数据。
那么就可以保证每个线程的各个模块取得的数据都是一致的。

public class ThreadScopeShareData3 {

private static Map threadData = new HashMap();
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();
for (int i = 0; i <2; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
int data2= new Random().nextInt(100);
System.out.println(Thread.currentThread().getName()+" put "+data2);
threadData.put(Thread.currentThread(),data2);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
new A().get();
new B().get();

}
});
}
threadPool.shutdown();
}

static class A{
public void get(){
int data = threadData.get(Thread.currentThread());
System.out.println("A from " + Thread.currentThread().getName()
+ " get data :" + data);
}
}
//省略class B
}

运行结果

pool-1-thread-1 put 2
pool-1-thread-2 put 99
A from pool-1-thread-2 get data :99
A from pool-1-thread-1 get data :2
B from pool-1-thread-1 get data :2
B from pool-1-thread-2 get data :99


ThreadLocal的简单介绍

   早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。
  当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

我们先看应用再讲原理,然后再讲一个实际的应用。
第一个应用

public class ThreadLocalTest {

private static ThreadLocal x = new ThreadLocal();
public static void main(String[] args) {
for(int i=0;i<2;i++){
new Thread(new Runnable(){
@Override
public void run() {
int data = new Random().nextInt(500);
System.out.println(Thread.currentThread().getName()
+ " has put data :" + data);
x.set(data);
new A().get();
new B().get();
}
}).start();
}
}

static class A{
public void get(){
int data = x.get();
System.out.println("A from " + Thread.currentThread().getName()
+ " get data :" + data);
}
}

static class B{
public void get(){
int data = x.get();
System.out.println("B from " + Thread.currentThread().getName()
+ " get data :" + data);
}
}
}


Thread-0 has put data :67
A from Thread-0 get data :67
B from Thread-0 get data :67
Thread-1 has put data :221
A from Thread-1 get data :221
B from Thread-1 get data :221
完全符合我们的要求。
这里有个问题:
如果一个线程能要共享多个变量怎么做?
private static ThreadLocal x = new ThreadLocal();
private static ThreadLocal y = new ThreadLocal();
不嫌麻烦吗?
ThreadLocal里可以放Interger,也可以放Objcet么。
如果几个变量有关系,如name,age我们就把它们包装成User;

如果变量没有关系,那就包装成一个map。

(当然一个线程如果要共享多个变量,那么分别设置为x,y也是可以的)

这样可以不?
import java.util.Random;

public class ThreadLocalTest3 {

private static ThreadLocal myThreadScopeData = new ThreadLocal();

public static void main(String[] args) {
for (int i = 0; i <2; i++) {
new Thread(new Runnable() {
@Override
public void run() {
int data = new Random().nextInt(500);

MyThreadScopeData2 myData = new MyThreadScopeData2();
myData.setName("name" + data);
myData.setAge(data);
myThreadScopeData.set(myData);

new A().get();
new B().get();
}
}).start();
}
}

static class A {
public void get() {
MyThreadScopeData2 myData = myThreadScopeData.get();

System.out
.println("A from " + Thread.currentThread().getName()
+ " getMyData: " + myData.getName() + ","
+ myData.getAge());
}
}

//省略class B
}

class MyThreadScopeData2 {

private static ThreadLocal map = new ThreadLocal();

private String name;
private int age;

//省略get set
}

可以,不过对用户来说暴露了ThreadLocal的应用,我们希望在调用的时候,ThreadLocal对用户是透明的。
换句话说,我们得把ThreadLocal包装起来。

import java.util.Random;

public class ThreadLocalTest2 {


public static void main(String[] args) {
for(int i=0;i<2;i++){
new Thread(new Runnable(){
@Override
public void run() {
int data = new Random().nextInt(500);

MyThreadScopeData.getThreadInstance().setName("name" + data);
MyThreadScopeData.getThreadInstance().setAge(data);
new A().get();
new B().get();
}
}).start();
}
}

static class A{
public void get(){

MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
System.out.println("A from " + Thread.currentThread().getName()
+ " getMyData: " + myData.getName() + "," +
myData.getAge());
}
}
//省略Class B
}

class MyThreadScopeData{
private MyThreadScopeData(){}
public static MyThreadScopeData getThreadInstance(){
MyThreadScopeData instance = map.get();
if(instance == null){
instance = new MyThreadScopeData();
map.set(instance);
}
return instance;
}

private static ThreadLocal map =
new ThreadLocal();

private String name;
private int age;
//省略getset
}

关于上面的单例模式可以参考
http://blog.csdn.net/lovelion/article/details/7420886



现在重头戏来了,看看ThreadLocal实现的原理。

参见:

http://blog.csdn.net/dlf123321/article/details/42531979



推荐阅读
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了在Java中gt、gtgt、gtgtgt和lt之间的区别。通过解释符号的含义和使用例子,帮助读者理解这些符号在二进制表示和移位操作中的作用。同时,文章还提到了负数的补码表示和移位操作的限制。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 从零学Java(10)之方法详解,喷打野你真的没我6!
    本文介绍了从零学Java系列中的第10篇文章,详解了Java中的方法。同时讨论了打野过程中喷打野的影响,以及金色打野刀对经济的增加和线上队友经济的影响。指出喷打野会导致线上经济的消减和影响队伍的团结。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • Spring学习(4):Spring管理对象之间的关联关系
    本文是关于Spring学习的第四篇文章,讲述了Spring框架中管理对象之间的关联关系。文章介绍了MessageService类和MessagePrinter类的实现,并解释了它们之间的关联关系。通过学习本文,读者可以了解Spring框架中对象之间的关联关系的概念和实现方式。 ... [详细]
author-avatar
btsk@163.com
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有