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. kafka学习之-KafkaOffsetMonitor后台监控

    1.下载Kafka Consumer Offset Monitor安装包 http://pan.baidu.com/s/1ntzIUPN 2.在/usr/local/hadoop路径下面建立放置Kaf ...

  2. Eclipse + PyDev 快捷键

    ●多行缩进(减少缩进):tab/shift+tab ●复制行: Ctrl+Alt+方向键'↓' ●删除行:Ctrl+d ●自动完成:Alt+/  ●注释:Ctrl+/ ●窗口最大小:Ctrl+m == ...

  3. Android学习之——ListView

    背景知识 ListView在Android应用中使用非常广泛,手机上必备的微博.网易新闻等,都使用了ListView.

  4. [MVC] 自定义ActionSelector,根据参数选择Action

    很多时候我们会根据UI传入的参数,呈现不同的View.也就是对于同一个Action如何根据请求数据返回不同的View.通常情况下我们会按照如下方法来写,例如: [AcceptVerbs(HttpVer ...

  5. 怎么安装ABBYY FineReader

    ABBYY FineReader是市场领先的文字识别(OCR)软件,可快速方便地将扫描纸质文档.PDF文件和数码相机的图像转换成可编辑.可搜索信息,ABBYY FineReader 12是目前最新版本 ...

  6. Vim终极指南:所思即所得

    https://blog.csdn.net/dc_726/article/details/78981756

  7. Go工具和调试详解

    https://blog.csdn.net/happyanger6/article/details/78724594/ https://blog.csdn.net/u012210379/article ...

  8. linux环境下pytesseract的安装和央行征信中心的登录验证码识别

    首先是安装,我参考的是这个 http://blog.csdn.net/xinghun_4/article/details/47860645 我是centos,使用yum yum install pyt ...

  9. [原]unity3d GLSL无法pc上显示

    pc编写GLSL: Shader "Custom/GLSL" {    Properties {        _MainTex ("Base (RGB)", ...

  10. 如何在O(n)的时间复杂度内找出数组中出现次数超过了一半的数

    方法一:每次取出两个不同的数,剩下的数字中重复出现次数超过一半的数字肯定,将规模缩小化.如果每次删除两个不同的数,这里当然不是真的把它们踢出数组,而是对于候选数来说,出现次数减一,对于其他数来说,循环 ...