ThreadLocal

文章来源:http://con.zhangjikai.com/ThreadLocal.html

ThreadLocal 主要用来提供线程局部变量,也就是变量只对当前线程可见。

线程局部变量

在多线程环境下,之所以会有并发问题,就是因为不同的线程会同时访问同一个共享变量,例如下面的形式

public class MultiThreadDemo {

    public static class Number {
private int value = ; public void increase() throws InterruptedException {
value = ;
Thread.sleep();
System.out.println("increase value: " + value);
} public void decrease() throws InterruptedException {
value = -;
Thread.sleep();
System.out.println("decrease value: " + value);
}
} public static void main(String[] args) throws InterruptedException {
final Number number = new Number();
Thread increaseThread = new Thread(new Runnable() {
@Override
public void run() {
try {
number.increase();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}); Thread decreaseThread = new Thread(new Runnable() {
@Override
public void run() {
try {
number.decrease();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}); increaseThread.start();
decreaseThread.start();
}
}

 在上面的代码中,increase 线程和 decrease 线程会操作同一个 number 中 value,那么输出的结果是不可预测的,因为当前线程修改变量之后但是还没输出的时候,变量有可能被另外一个线程修改

一种解决方法是在 increase() 和 decrease() 方法上加上 synchronized 关键字进行同步,这种做法其实是将 value 的 赋值 和 打印 包装成了一个原子操作,

也就是说两者要么同时进行,要不都不进行,中间不会有额外的操作。我们换个角度考虑问题,如果 value 只属于 increase 线程或者 decrease 线程,而不是

被两个线程共享,那么也不会出现竞争问题。一种比较常见的形式就是局部(local)变量(这里排除局部变量引用指向共享对象的情况),如下所示:

public void increase() throws InterruptedException {
int value = ;
Thread.sleep();
System.out.println("increase value: " + value);
}

不论 value 值如何改变,都不会影响到其他线程,因为在每次调用 increase 方法时,都会创建一个 value 变量,该变量只对当前调用 increase 方法的线程可见。借助于这种思想,我们可以对每个线程创建一个共享变量的副本,该副本只对当前线程可见(可以认为是线程私有的变量),那么修改该副本变量时就不会影响到其他的线程。一个简单的思路是使用 Map 存储每个变量的副本,将当前线程的 id 作为 key,副本变量作为 value 值,下面是一个实现:

public class SimpleImpl {

    public static class CustomThreadLocal {
private Map<Long, Integer> cacheMap = new HashMap<>(); private int defaultValue ; public CustomThreadLocal(int value) {
defaultValue = value;
} public Integer get() {
long id = Thread.currentThread().getId();
if (cacheMap.containsKey(id)) {
return cacheMap.get(id);
}
return defaultValue;
} public void set(int value) {
long id = Thread.currentThread().getId();
cacheMap.put(id, value);
}
} public static class Number {
private CustomThreadLocal value = new CustomThreadLocal(); public void increase() throws InterruptedException {
value.set();
Thread.sleep();
System.out.println("increase value: " + value.get());
} public void decrease() throws InterruptedException {
value.set(-);
Thread.sleep();
System.out.println("decrease value: " + value.get());
}
} public static void main(String[] args) throws InterruptedException {
final Number number = new Number();
Thread increaseThread = new Thread(new Runnable() {
@Override
public void run() {
try {
number.increase();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}); Thread decreaseThread = new Thread(new Runnable() {
@Override
public void run() {
try {
number.decrease();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}); increaseThread.start();
decreaseThread.start();
}
}

但是上面的实现会存在下面的问题:

  • 每个线程对应的副本变量的生命周期不是由线程决定的,而是由共享变量的生命周期决定的。在上面的例子中,即便线程执行完,只要 number 变量存在,线程
  • 的副本变量依然会存在(存放在 number 的 cacheMap 中)。但是作为特定线程的副本变量,该变量的生命周期应该由线程决定,线程消亡之后,该变量也应
  • 该被回收。
  • 多个线程有可能会同时操作 cacheMap,需要对 cacheMap 进行同步处理。

为了解决上面的问题,我们换种思路,每个线程创建一个 Map,存放当前线程中副本变量,用 CustomThreadLocal 的实例作为 key 值,下面是一个示例:

public class SimpleImpl2 {

    public static class CommonThread extends Thread {
Map<Integer, Integer> cacheMap = new HashMap<>();
} public static class CustomThreadLocal {
private int defaultValue; public CustomThreadLocal(int value) {
defaultValue = value;
} public Integer get() {
Integer id = this.hashCode();
Map<Integer, Integer> cacheMap = getMap();
if (cacheMap.containsKey(id)) {
return cacheMap.get(id);
}
return defaultValue;
} public void set(int value) {
Integer id = this.hashCode();
Map<Integer, Integer> cacheMap = getMap();
cacheMap.put(id, value);
} public Map<Integer, Integer> getMap() {
CommonThread thread = (CommonThread) Thread.currentThread();
return thread.cacheMap;
}
} public static class Number {
private CustomThreadLocal value = new CustomThreadLocal(); public void increase() throws InterruptedException {
value.set();
Thread.sleep();
System.out.println("increase value: " + value.get());
} public void decrease() throws InterruptedException {
value.set(-);
Thread.sleep();
System.out.println("decrease value: " + value.get());
}
} public static void main(String[] args) throws InterruptedException {
final Number number = new Number();
Thread increaseThread = new CommonThread() {
@Override
public void run() {
try {
number.increase();
} catch (InterruptedException e) {
e.printStackTrace();
} }
}; Thread decreaseThread = new CommonThread() {
@Override
public void run() {
try {
number.decrease();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
increaseThread.start();
decreaseThread.start();
}
}

在上面的实现中,当线程消亡之后,线程中 cacheMap 也会被回收,它当中存放的副本变量也会被全部回收,并且 cacheMap 是线程私有的,不会出现多个线程同时访问

一个 cacheMap 的情况。在 Java 中,ThreadLocal 类的实现就是采用的这种思想,注意只是思想,实际的实现和上面的并不一样。

Java 使用 ThreadLocal 类来实现线程局部变量模式,ThreadLocal 使用 set 和 get 方法设置和获取变量,下面是函数原型:

public void set(T value);
public T get();

下面是使用 ThreadLocal 的一个完整示例:

public class ThreadLocalDemo {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
private static int value = ; public static class ThreadLocalThread implements Runnable {
@Override
public void run() {
threadLocal.set((int)(Math.random() * ));
value = (int) (Math.random() * );
try {
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf(Thread.currentThread().getName() + ": threadLocal=%d, value=%d\n", threadLocal.get(), value);
}
} public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new ThreadLocalThread());
Thread thread2 = new Thread(new ThreadLocalThread());
thread.start();
thread2.start();
thread.join();
thread2.join();
}
}

我们看到虽然 threadLocal 是静态变量,但是每个线程都有自己的值,不会受到其他线程的影响。

ThreadLocal Java并发的更多相关文章

  1. Java并发编程:ThreadLocal

    Java并发编程:深入剖析ThreadLocal   Java并发编程:深入剖析ThreadLocal 想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadLocal的使用 ...

  2. Java并发(二十):线程本地变量ThreadLocal

    ThreadLocal是一个本地线程副本变量工具类. 主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不同的 ...

  3. java并发编程目录

    java并发编程目录 Java多线程基础:进程和线程之由来 JAVA多线程实现的四种方式 Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition Jav ...

  4. java 并发(五)---AbstractQueuedSynchronizer

    文章部分图片和代码来自参考文章. LockSupport 和 CLH 和 ConditionObject 阅读源码首先看一下注解 ,知道了大概的意思后,再进行分析.注释一开始就进行了概括.AQS的实现 ...

  5. Java并发编程:深入剖析ThreadLocal(转载)

    Java并发编程:深入剖析ThreadLocal(转载) 原文链接:Java并发编程:深入剖析ThreadLocal 想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadL ...

  6. 【Java 并发】详解 ThreadLocal

    前言 ThreadLocal 主要用来提供线程局部变量,也就是变量只对当前线程可见,本文主要记录一下对于 ThreadLocal 的理解.更多关于 Java 多线程的文章可以转到 这里. 线程局部变量 ...

  7. (转)Java并发编程:深入剖析ThreadLocal

    Java并发编程:深入剖析ThreadLoca Java并发编程:深入剖析ThreadLocal 说下自己的理解:使用ThreadLocal能够实现空间换时间,重在理解ThreadLocal是如何复制 ...

  8. [转载]Java并发编程:深入剖析ThreadLocal

                原文地址:http://www.cnblogs.com/dolphin0520/p/3920407.html 想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨 ...

  9. 【转载】 Java并发编程:深入剖析ThreadLocal

    原文链接:http://www.cnblogs.com/dolphin0520/p/3920407.html感谢作者的辛苦总结! Java并发编程:深入剖析ThreadLocal 想必很多朋友对Thr ...

随机推荐

  1. Android 虚拟键盘弹出把底部栏顶上去的解决办法

    在AndroidManifest中使用ActivityGroup的activity中加上:android:windowSoftInputMode="adjustPan"

  2. [转载]Android 生成keystore,两种方式

    Refer : http://blog.csdn.net/ms03001620/article/details/8490314 一.eclipse 中生成android keystore 建立任意一个 ...

  3. Golang优秀开源项目汇总

    https://blog.csdn.net/hackstoic/article/details/52008307

  4. Java利用while循环计算1+1/2!+1/3!……+1/20!

    编写程序,用while语句计算1+1/2!+1/3!……+1/20!,并在控制泰山输出计算结果.要求1+1/2!+1/3!……+1/20!,其实就是求1+1*1/2+1*1/2*1/3+……+1*1/ ...

  5. GoF--适配器设计模式

    1.概念:  适配器模式(Adapter Pattern)把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作. 2.形式  a.类的适配器模式  ...

  6. swoole的进程模型架构

    swoole的强大之处就在与其进程模型的设计,既解决了异步问题,又解决了并行. 主线程MainReactor swoole启动后主线程会负责监听server socket,如果有新的连接accept, ...

  7. python3.5 中Django框架连接mysql

    ps:mysqldb目前还不支持3.0python唉,最近赶了个新潮,用起了Python3.4跟Django1.6,数据库依然是互联网企业常见的MySql.悲催的是在Python2.7时代连接MySq ...

  8. 第一个map reduce程序

    完成了第一个mapReduce例子,记录一下. 实验环境: hadoop在三台ubuntu机器上部署 开发在window7上进行 hadoop版本2.2.0 下载了hadoop-eclipse-plu ...

  9. Splash resource_timeout 属性

    resource_timeout属性用于设置加载的超时时间,单位是秒,如果设置为 0 代表不检测超时,如下,设置超时时间为 0.1 秒: function main(splash) splash.re ...

  10. Unity Shader 获取模型空间坐标

    CGPROGRAM // Physically based Standard lighting model, and enable shadows on all light types #pragma ...