在前面的文章(6. 线程范围内共享数据)总结了一下,线程范围内的数据共享问题,即定义一个 Map,将当前线程名称和线程中的数据以键值对的形式存到 Map 中,然后在当前线程中使用数据的时候就可以根据当前线程名称从 Map 中拿到当前线程中的数据,这样就可以做到不同线程之间数据互不干扰。其实 ThreadLocal 类就是给我们提供了这个解决方法,所以我们完全可以用 ThreadLocal 来完成线程范围内数据的共享。

public class ThreadScopeShareData {
//定义一个ThreadLocal
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(); 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();
System.out.println(Thread.currentThread().getName() + " has put a data: " + data);
threadLocal.set(data);//直接往threadLocal里面里面扔数据即可
new TestA().getData();
new TestB().getData();
}
}).start();
}
} static class TestA {
public void getData() {
System.out.println("A get data from " + Thread.currentThread().getName() + ": " + threadLocal.get());//直接取,不用什么关键字,它直接从当前线程中取
}
} static class TestB {
public void getData() {
System.out.println("B get data from " + Thread.currentThread().getName() + ": " + threadLocal.get());//直接取,不用什么关键字,它直接从当前线程中取
}
}
}

结合第 6 节的代码,可以看出,其实 ThreadLocal 就相当于一个 Map,只不过我们不需要设定 key 了,它默认就是当前的 Thread,往里面放数据,直接 set 即可,取数据,直接 get 即可,很方便,就不用 Map 一个个存了,

但是问题来了,ThreadLocal 虽然存取方便,但是 get() 方法中根本没有参数,也就是说我们只能往 ThreadLocal 中放一个数据,多了就不行了,那么该如何解决这个问题呢?

很明显,ThreadLocal 是个容器,且只能存一下,那么如果有多个数据,我们可以定义一个类,把数据都封装到这个类中,然后扔到 ThreadLocal 中,用的时候取这个类,再从类中去我们想要的数据即可。

好,现在有两个线程,每个线程都要操作各自的数据,而且数据有两个:名字和年龄。根据上面的思路,写一个 demo,如下:

public class ThreadScopeShareData {

	private static ThreadLocal<User> threadLocal = new ThreadLocal<User>();

	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();
System.out.println(Thread.currentThread().getName() + " has put a data: " + data); //每个线程中维护一个User,User中保存了name和age
User user = new User();
user.setName("name" + data);
user.setAge(data);
threadLocal.set(user); //向当前线程中存入user对象 new TestA().getData();
new TestB().getData();
}
}).start();
}
} static class TestA {
public void getData() { User user = threadLocal.get();//从当前线程中取出user对象
System.out.println("A get data from " + Thread.currentThread().getName() + ": "
+ user.getName() + "," + user.getAge());
}
} static class TestB {
public void getData() { User user = threadLocal.get();//从当前线程中取出user对象
System.out.println("B get data from " + Thread.currentThread().getName() + ": "
+ user.getName() + "," + user.getAge()); }
} }
//定义一个User类来存储姓名和年龄
class User { private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}

这样进行一下封装就可以实现多个数据的存储了,但是上面这个程序是不太好的,原因很明显,在线程中,我要自己 new 一个对象,然后对其进行操作,最后还得把这个对象扔到当前线程中。这不太符合设计的思路,设计的思路应该是这样的,不能让用户自己去 new 啊,如果有个类似于 getThreadInstance() 的方法,用户想要从 ThreadLocal 中拿什么对象就用该对象去调用这个 getThreadInstance() 方法多好,这样拿到的永远都是本线程范围内的对象了。

这让我想到了学习 JDBC 的时候,从 ThreadLocal 中拿 connection 时的做法了,如果当前ThreadLocal中有就拿出来,没有就产生一个,这跟这里的需求是一样的,我想要一个User,那我应该用User去调用getThreadLInstance()方法获取本线程中的一个User对象,如果有就拿,如果没有就产生一个。完全一样的思路。这个设计跟单例的模式有点像,这里说有点像不是本质上像,是代码结构很像。先看一下简单的单例模式代码结构:

public class Singleton {
private static Singleton instance = null;
private Singleton() {//私有构造方法阻止外界new
}
public static synchronized Singleton getInstance() { //提供一个公共方法返回给外界一个单例的实例
if (instance == null) { //如果没有实例
instance = new Singleton(); //就新new一个
}
return instance; //返回该实例
}
}

这是懒汉式单例模式的代码结构,我门完全可以效仿该思路去设计一个从当前线程中拿 User 的办法,所以将程序修改如下:

public class ThreadScopeShareData {
//不需要在外面定义threadLocal了,放到User类中了
// private static ThreadLocal<User> threadLocal = new ThreadLocal<User>(); 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();
System.out.println(Thread.currentThread().getName() + " has put a data: " + data); //这里直接用User去调用getThreadLocal这个静态方法获取本线程范围内的一个User对象
//这里就优雅多了,我完全不用关心如何去拿该线程中的对象,如何把对象放到threadLocal中
//我只要拿就行,而且拿出来的肯定就是当前线程中的对象,原因看下面User类中的设计
User.getThreadInstance().setName("name" + data);
User.getThreadInstance().setAge(data); new TestA().getData();
new TestB().getData();
}
}).start();
}
} static class TestA {
public void getData() {
//还是调用这个静态方法拿,因为刚刚已经拿过一次了,threadLocal中已经有了
User user = User.getThreadInstance();
System.out.println("A get data from " + Thread.currentThread().getName() + ": "
+ user.getName() + "," + user.getAge());
}
} static class TestB {
public void getData() { User user = User.getThreadInstance();
System.out.println("A get data from " + Thread.currentThread().getName() + ": "
+ user.getName() + "," + user.getAge());
}
} } class User { private User() {} private static ThreadLocal<User> threadLocal = new ThreadLocal<User>(); //注意,这不是单例,每个线程都可以new,所以不用synchronized,
//但是每个threadLocal中是单例的,因为有了的话就不会再new了
public static /*synchronized*/ User getThreadInstance() {
User instance = threadLocal.get(); //先从当前threadLocal中拿
if(instance == null) {
instance = new User();
threadLocal.set(instance);//如果没有就新new一个放到threadLocal中
}
return instance; //向外返回该User
} private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}

经过这样的改造,代码就优雅多了,外界从来不要考虑如何去当前线程中拿数据,只要拿就行,拿出来的肯定就是当前线程中你想要的对象,因为在对象内部已经写好了这个静态方法了,而且拿出来操作完了后,也不需要再放到 threadLocal 中,因为它本来就在 threadLocal 中,这就封装的相当好了。

ThreadLocal类的应用和使用技巧就总结这么多吧~如有问题,欢迎交流,我们一起进步!

Java并发基础07. ThreadLocal类以及应用技巧的更多相关文章

  1. Java并发编程之ThreadLocal类

    ThreadLocal类可以理解为ThreadLocalVariable(线程局部变量),提供了get与set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回当 ...

  2. Java 并发基础

    Java 并发基础 标签 : Java基础 线程简述 线程是进程的执行部分,用来完成一定的任务; 线程拥有自己的堆栈,程序计数器和自己的局部变量,但不拥有系统资源, 他与其他线程共享父进程的共享资源及 ...

  3. java并发基础及原理

    java并发基础知识导图   一 java线程用法 1.1 线程使用方式 1.1.1 继承Thread类 继承Thread类的方式,无返回值,且由于java不支持多继承,继承Thread类后,无法再继 ...

  4. java并发基础(五)--- 线程池的使用

    第8章介绍的是线程池的使用,直接进入正题. 一.线程饥饿死锁和饱和策略 1.线程饥饿死锁 在线程池中,如果任务依赖其他任务,那么可能产生死锁.举个极端的例子,在单线程的Executor中,如果一个任务 ...

  5. java并发基础(二)

    <java并发编程实战>终于读完4-7章了,感触很深,但是有些东西还没有吃透,先把已经理解的整理一下.java并发基础(一)是对前3章的总结.这里总结一下第4.5章的东西. 一.java监 ...

  6. Java并发基础概念

    Java并发基础概念 线程和进程 线程和进程都能实现并发,在java编程领域,线程是实现并发的主要方式 每个进程都有独立的运行环境,内存空间.进程的通信需要通过,pipline或者socket 线程共 ...

  7. 【搞定 Java 并发面试】面试最常问的 Java 并发基础常见面试题总结!

    本文为 SnailClimb 的原创,目前已经收录自我开源的 JavaGuide 中(61.5 k Star![Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识.欢迎 Sta ...

  8. Java并发编程:Thread类的使用

    Java并发编程:Thread类的使用 在前面2篇文章分别讲到了线程和进程的由来.以及如何在Java中怎么创建线程和进程.今天我们来学习一下Thread类,在学习Thread类之前,先介绍与线程相关知 ...

  9. 【转】Java并发编程:Thread类的使用

    一.线程的状态 在正式学习Thread类中的具体方法之前,我们先来了解一下线程有哪些状态,这个将会有助于对Thread类中的方法的理解. 线程从创建到最终的消亡,要经历若干个状态.一般来说,线程包括以 ...

随机推荐

  1. 《一步步成为 Hacker_Day 01》

    环境搭建 传统运行模式 - 一台机器同时只能运行一个操作系统 |:----------|----------:| | 应用程序 | 应用程序 | |:----------|----------:| | ...

  2. 简说Python之图形初体验

    针对孩子,最容易引起小孩的感官认知的就是图形.因此,系统运用图形编程,可以更好地让孩子喜欢上编程. turtle叫做,Turtle graphics.是python第三方的画图模块工具.可以通过imp ...

  3. Lucene查询语法汇总

    目录 一.单词查询 二.通配符查询 三.模糊查询 四.近似查询 五.范围查询 六.优先级查询 七.逻辑操作 八.括号分组 九.转义特殊字符 Lucene是目前最为流行的开源全文搜索引擎工具包,提供了完 ...

  4. 深入理解计算机系统 (CS:APP) Lab2 - Bomb Lab 解析

    原文地址:https://billc.io/2019/04/csapp-bomblab/ 写在前面 CS:APP是这学期的一门硬核课程,应该是目前接触到最底层的课程了.学校的教学也是尝试着尽量和CMU ...

  5. 【Weiss】【第03章】练习3.13:桶排序

    [练习3.13] 利用社会安全号码对学生记录构成的数组排序.编写一个程序进行这件工作,使用具有1000个桶的基数排序并且分三趟进行. Answer: 首先,对社会安全号码不了解的就把它当成一个不超过9 ...

  6. Java多线程并发07——锁在Java中的实现

    上一篇文章中,我们已经介绍过了各种锁,让各位对锁有了一定的了解.接下来将为各位介绍锁在Java中的实现.关注我的公众号「Java面典」了解更多 Java 相关知识点. 在 Java 中主要通过使用sy ...

  7. BeanShell断言:根据响应的2个数据的比较结果来决定断言

    需求:目前有一个请求,请求的响应中有2个值,aaa和bbb,我们比较aaa和bbb,如果aaa大于bbb,则断言通过,否则失败. 1.添加一个Dummy Sampler,模拟请求. 2.添加2个正则表 ...

  8. nmon使用问题汇总(不定期更新)

    nmon使用问题汇总 1.nmon常用命令: ./nmon -s1 -c300 -f -m /root/nmon-test-result/项目-50并发/ 2.设置nmon参数为-s1 -c720,发 ...

  9. requests模块使用一

    1.安装与简介 Urllib和requests模块是python中发起http请求最常见的模块,但是requests模块使用更加方便简单. pip install requests 2.GET请求 2 ...

  10. iOS 指纹认证登陆开发(TouchID)

    设计思路 TouchID 关联账号 用户登陆成功 -> 开启TouchID登陆 -> TouchID验证 -> 记录用户信息(验证通过) -> 关联完成 TouchID 登陆 ...