ThreadLocal

什么是ThreadLocal?

顾名思义它是local variable(线程局部变量)。它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。

从线程的角度看,就好像每一个线程都完全拥有该变量。

注意:ThreadLocal不是用来解决共享对象的多线程访问问题的。

一、多线程共享成员变量

在多线程环境下,之所以会有并发问题,就是因为不同的线程会同时访问同一个共享变量,同时进行一系列的操作。

1、例如下面的形式

//这个意思很简单,创建两个线程,a线程对全局变量+10,b线程对全局变量-10
public class MultiThreadDemo { public static class Number {
private int value = 0; public void increase() throws InterruptedException {
//这个变量对于该线程属于局部变量
value = 10;
Thread.sleep(10);
System.out.println("increase value: " + value);
} public void decrease() throws InterruptedException {
//同样这个变量对于该线程属于局部变量
value = -10;
Thread.sleep(10);
System.out.println("decrease value: " + value);
}
} public static void main(String[] args) throws InterruptedException {
final Number number = new Number();
Thread a = new Thread(new Runnable() {
@Override
public void run() {
try {
number.increase();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}); Thread b = new Thread(new Runnable() {
@Override
public void run() {
try {
number.decrease();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}); a.start();
b.start();
}
}

思考:可能运行的结果:

/*运行结果(一种可能)
increase value: -10
decrease value: -10
*
*你或许在想不对啊,按常理不是一个输出10,一个输出-10嘛
*原因分析:
*其实很简单,就是当a执行value = 10时,还没有等到下面输出,这个时候
* b线程获得cpu执行权value = -10;这个时候a在获得cpu执行权的时候输出当然是-10。
* 这里的根本原因是线程的赋值和输出一起不是原子性的。
*/

运行结果

为了验证我上面的原因分析,我修改下代码:

 public    void decrease() throws InterruptedException {
//我在decrease()新添加这个输出,看下输出结果
System.out.println("increase value: " + value);
value = -10;
Thread.sleep(10);
System.out.println("decrease value: " + value);
}

再看运行结果:(和上面分析的一样)

思考:如果在 private volatile  int value = 0;在这里加上volatile关键字结果如何?

/*结果会和上面没有任何区别,为什么
*volatile的特点是保证可见性,但不保证原子性,你这a获得cpu改成value = 10,
*这个时候b获得线程,它是知道value变成10了,但不影响它在把值赋值成-10。
*/

volatile结果

所以总的来说:

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

当如如果要保证输出我当前线程的值呢?

其实也很简单:在 increase() 和 decrease() 方法上加上 synchronized 关键字进行同步,这种做法其实是将 value 的 赋值 和 打印 包装成了一个原子操作,也就是说两者要么同时进行,要不都不进行,中间不会有额外的操作。

二、多线程不共享全局变量

上面的例子我们可以看到a线程操作全局变量,b在去去全局成员变量是a已经修改过的。

如果我们需要 value 只属于 increase 线程或者 decrease 线程,而不是被两个线程共享,那么也不会出现竞争问题。

1、方式一

很简单,为每一个线程定义一份只属于自己的局部变量。

 public void increase() throws InterruptedException {
//为每一个线程定义一个局部变量,这样当然就是线程私有的
int value = 10;
Thread.sleep(10);
System.out.println("increase value: " + value);
}

不论 value 值如何改变,都不会影响到其他线程,因为在每次调用 increase 方法时,都会创建一个 value 变量,该变量只对当前调用 increase 方法的线程可见。

2、方式二

借助于上面这种思想,我们可以创建一个map,将当前线程的 id 作为 key,副本变量作为 value 值,下面是一个实现

public class SimpleImpl {

    //这个相当于工具类
public static class CustomThreadLocal {
//创建一个Map
private Map<Long, Integer> cacheMap = new HashMap<>(); private int defaultValue ; public CustomThreadLocal(int value) {
defaultValue = value;
} //进行封装一层,其实就是通过key得到value
public Integer get() {
long id = Thread.currentThread().getId();
if (cacheMap.containsKey(id)) {
return cacheMap.get(id);
}
return defaultValue;
}
//同样存放key,value
public void set(int value) {
long id = Thread.currentThread().getId();
cacheMap.put(id, value);
}
}
//这个类引用工具类,当然也可以在这里写map。
public static class Number {
private CustomThreadLocal value = new CustomThreadLocal(0); public void increase() {
value.set(10);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("increase value: " + value.get());
} public void decrease() {
value.set(-10);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("decrease value: " + value.get());
}
} public static void main(String[] args) throws InterruptedException {
final Number number = new Number();
Thread a = new Thread(new Runnable() {
@Override
public void run() {
number.increase();
}
}); Thread b = new Thread(new Runnable() {
@Override
public void run() {
number.decrease();
}
}); a.start();
b.start();
}
}

思考,运行结果如何?

//运行结果(其中一种):
increase value: 0
decrease value: -10

按照常理来讲应该是一个10,一个-10,怎么都想不通会出现0,也没有想明白是哪个地方引起的这个线程不同步,毕竟我这里两个线程各放各的key和value值,而且key也不一样

为什么出现有一个不存在key值,而取出默认值0。

其实原因就在HashMap是线程不安全的,并发的时候设置值,可能导致冲突,另一个没设置进去。如果这个改成Hashtable,就发现永远输出10和-10两个值。

三、ThreadLocal

其实上面的方式二实现的功能和ThreadLocal像,只不过ThreadLocal肯定更完美。

1、了解ThreadLocal类提供的几个方法

   public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }

get()方法:获取ThreadLocal在当前线程中保存的变量副本。

set()方法:用来设置当前线程中变量的副本。

remove()方法:用来移除当前线程中变量的副本。

initialValue()方法:是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法,下面会详细说明。

这里主要看get和set方法源码

public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
} public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

通过这个可以总结出:

(1)get和set底层还是一个ThreadLocalMap实现存取值

(2)我们在放的时候只放入value值,那么它的key其实就是ThreadLocal类的实例对象(也就是当前线程对象)

2、小案例

public class Test {
//创建两个ThreadLocal对象
ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
ThreadLocal<String> stringLocal = new ThreadLocal<String>(); public static void main(String[] args) throws InterruptedException {
final Test test = new Test();
ExecutorService executors= Executors.newFixedThreadPool(2);
executors.execute(new Runnable() {
@Override
public void run() {
test.longLocal.set(Thread.currentThread().getId());
test.stringLocal.set(Thread.currentThread().getName());
System.out.println(test.longLocal.get());
System.out.println(test.stringLocal.get());
}
});
executors.execute(new Runnable() {
@Override
public void run() {
test.longLocal.set(Thread.currentThread().getId());
test.stringLocal.set(Thread.currentThread().getName());
System.out.println(test.longLocal.get());
System.out.println(test.stringLocal.get());
}
});
}
}

思考,运行结果如何?

//运行结果(其中一种可能)
11
10
pool-1-thread-2
pool-1-thread-1
//说明已经实现了共享变量私有

运行结果

四、ThreadLocal的应用场景

最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。

1、 数据库连接管理

同一事务多DAO共享同一Connection,必须在一个共同的外部类使用ThreadLocal保存Connection。

public class ConnectionManager {    

    private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
@Override
protected Connection initialValue() {
Connection conn = null;
try {
conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test", "username",
"password");
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
}; public static Connection getConnection() {
return connectionHolder.get();
} public static void setConnection(Connection conn) {
connectionHolder.set(conn);
}
}

这样就保证了一个线程对应一个数据库连接,保证了事务。因为事务是依赖一个连接来控制的,如commit,rollback,都是数据库连接的方法。

2、Session管理

private static final ThreadLocal threadSession = new ThreadLocal();

public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}

参考

1、【Java 并发】详解 ThreadLocal

2、Java并发编程:深入剖析ThreadLocal

3、对ThreadLocal中的key和value

想太多,做太少,中间的落差就是烦恼。想没有烦恼,要么别想,要么多做。少校【12】

java多线程(6)---ThreadLocal的更多相关文章

  1. Java多线程之 ThreadLocal

    一.什么是ThreadLocal? 顾名思义它是local variable(线程局部变量).它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副 ...

  2. Java多线程基础-ThreadLocal

    感谢原文作者:Yuicon 原文链接:https://segmentfault.com/a/1190000016705955 序 在多线程环境下,访问非线程安全的变量时必须进行线程同步,例如使用 sy ...

  3. java 多线程(threadlocal)

    package com.example; import java.util.Random; public class App { public static class MyRunnable1 imp ...

  4. java多线程学习-ThreadLocal

    为了凑字,把oracle文档里介绍ThreadLocal抄过来 public class ThreadLocal<T> extends Object This class provides ...

  5. Java多线程:ThreadLocal

    一.ThreadLocal基础知识 ThreadLocal是线程的一个本地化对象,或者说是局部变量.当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的 ...

  6. java 多线程 day06 threadLocal

    import java.util.HashMap;import java.util.Map;import java.util.Random; /** * Created by chengtao on ...

  7. java 多线程 :ThreadLocal 共享变量多线程不同值方案;InheritableThreadLocal变量子线程中自定义值,孙线程可继承

      ThreadLocal类的使用 变量值的共享可以使用public static变量的形式,所有的线程都是用同一个public static变量.如果想实现每一个线程都有自己的值.该变量可通过Thr ...

  8. java多线程--------深入分析 ThreadLocal 内存泄漏问题

    前言 ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度.但是如果滥用ThreadLocal,就可能 ...

  9. java 多线程 一个博客

    http://blog.csdn.net/a352193394/article/category/2563875 Java多线程之~~~线程安全容器的非阻塞容器 在并发编程中,会经常遇到使用容器.但是 ...

  10. Java多线程系列——从菜鸟到入门

    持续更新系列. 参考自Java多线程系列目录(共43篇).<Java并发编程实战>.<实战Java高并发程序设计>.<Java并发编程的艺术>. 基础 Java多线 ...

随机推荐

  1. 1.用代码演示String类中的以下方法的用法 (2018.08.09作业)

    public class Test_001 { public static void main(String[] args) { String a = "德玛西亚!"; Strin ...

  2. 在Eclipse中使用git把项目导入到git中--转载

    [转载出处注明:http://www.zhangxiaofu.cn/java/commonTools/2015/0607/764.html] 一.原有项目:  项目名为TestGit 二.在osc@g ...

  3. python基础知识练习题(二)

    1. 有两个列表 l1 = [11, 22, 33] l2 = [22, 33, 44] a.获取内容相同的元素列表 li = []l1 = [11, 22, 33] l2 = [22, 33, 44 ...

  4. E - Elevator

    E - Elevatorhttp://codeforces.com/gym/241680/problem/E同余最短路,从0~a-1中每一个i向(i+b)%a连一条权值为b的边,向(i+c)%a连一条 ...

  5. 【转】腾讯云-解决Winscp permission denied的问题

    刚刚注册完腾讯云,因为要用来跑作业代码,所以操作系统选择的Ubuntu 16.04 32位 用Winscp登陆之后出现了错误代码为3的permission denied的错误,不能创建路径,也不能上传 ...

  6. 展开被 SpringBoot 玩的日子 《 二 》 WEB 开发

    上篇文章介绍了Spring boot初级教程 :< spring boot(一):入门篇 >,方便大家快速入门.了解实践Spring boot特性:本篇文章接着上篇内容继续为大家介绍spr ...

  7. Network Security final project---War Game

    项目介绍: 为自己的网段设置防火墙并尝试攻击其他组 网络结构: 每组有3个机器,包含一个gateway和两个workstation,其中gateway是可以连接到其他组的gateway,但是无法连接到 ...

  8. 用clumsy模拟丢包测试socket库的失败重传

    用python的socket库写了通信小程序,现在我需要通过软件模拟出在网络极差的情况下,socket底层解决丢包问题的能力怎么样,我一开始想的是分别在linux和windowns下分别测试,后来一想 ...

  9. 把.zip文件转化为.tar.gz文件

    工作中正好用到上传tar.gz文件,没有现成的转换工具,就写了方法转换 #encoding: utf-8import osimport tarfileimport zipfileimport osim ...

  10. Android进程间通信IPC

    一.IPC的说明 IPC是Inter-Process Communication的缩写,含义为进程间通信或跨进程通信,是指两个进程之间进行数据交换的过程. IPC不是Android独有的,任何一个操作 ...