Java并发基础06. 线程范围内共享数据
假设现在有个公共的变量 data,有不同的线程都可以去操作它,如果在不同的线程对 data 操作完成后再去取这个 data,那么肯定会出现线程间的数据混乱问题,因为 A 线程在取 data 数据前可能 B 线程又对其进行了修改,下面写个程序来说明一下该问题:
public class ThreadScopeShareData {
private static int data = 0;//公共的数据
public static void main(String[] args) {
for(int i = 0; i < 2; i ++) { //开启两个线程
new Thread(new Runnable() {
@Override
public void run() {
int temp = new Random().nextInt();
System.out.println(Thread.currentThread().getName() + " has put a data: " + temp); //打印出来为了看效果
data = temp; //操作数据:赋新值
new TestA().getData();
new TestB().getData();
}
}).start();
}
}
static class TestA {
public void getData() {
System.out.println("A get data from " + Thread.currentThread().getName() + ": " + data);//取出公共数据data
}
}
static class TestB {
public void getData() {
System.out.println("B get data from " + Thread.currentThread().getName() + ": " + data);
}
}
}
来看一下打印出来的结果:
Thread-0 has put a data: -1885917900
Thread-1 has put a data: -1743455464
A get data from Thread-0: -1743455464
A get data from Thread-1: -1743455464
B get data from Thread-1: -1743455464
B get data from Thread-0: -1743455464
从结果中可以看出,两次对 data 赋的值确实不一样,但是两个线程最后打印出来的都是最后赋的那个值,说明 Thread-0 拿出的数据已经不对了,这就是线程间共享数据带来的问题。
当然,我们完全可以使用 synchronized 关键字将 run()
方法中的几行代码给套起来,这样每个线程各自执行完,打印出各自的信息,这是没问题的,确实可以解决上面的线程间共享数据问题。但是,这是以其他线程被阻塞为代价的,即 Thread-0 在执行的时候,Thread-1 就被阻塞了,必须等待 Thread-0 执行完了才能执行。
那么如果我想两个线程同时跑,并且互不影响各自取出的值,该怎么办呢?这也是本文所要总结的重点,解决该问题的思想是:虽然现在都在操作公共数据 data,但是不同的线程本身对这个 data 要维护一个副本,这个副本不是线程间所共享的,而是每个线程所独有的,所以不同线程中所维护的 data 是不一样的,最后取的时候,是哪个线程,我就从哪个线程中取该 data。
基于上面这个思路,我再把上面的程序做一修改,如下:
public class ThreadScopeShareData {
private static int data = 0;//公共的数据
//定义一个Map以键值对的方式存储每个线程和它对应的数据,即Thread:data
private static Map<Thread, Integer> threadData = Collections.synchronizedMap(new HashMap<Thread, Integer>());
public static void main(String[] args) {
for(int i = 0; i < 2; i ++) {
new Thread(new Runnable() {
@Override
public void run() {
int temp = new Random().nextInt();
System.out.println(Thread.currentThread().getName() + " has put a data: " + temp); //打印出来为了看效果
threadData.put(Thread.currentThread(), temp); //向Map中存入本线程data数据的一个副本
data = temp; //操作数据:赋新值
new TestA().getData();
new TestB().getData();
}
}).start();
}
}
static class TestA {
public void getData() {
System.out.println("A get data from " + Thread.currentThread().getName() + ": "
+ threadData.get(Thread.currentThread())); //取出各线程维护的那个副本
}
}
static class TestB {
public void getData() {
System.out.println("B get data from " + Thread.currentThread().getName() + ": "
+ threadData.get(Thread.currentThread()));
}
}
}
上面程序中维护了一个 Map,键值对分别是线程和它的数据,那么在操作 data 的时候,先把各自的数据保存到这个 Map 中,这样每个线程保存的肯定不同,当再取的时候,根据当前线程对象作为 key 来取出对应的 data 副本,这样不同的线程之间就不会相互影响了。这个 HashMap 也需要包装一下,因为 HashMap 是非线程安全的,上面的程序中,不同的线程有对 HashMap 进行写操作,就有可能产生并发问题,所以也要包装一下。最后来看一下执行结果:
Thread-0 has put a data: 1817494992
Thread-1 has put a data: -1189758355
A get data from Thread-0: 1817494992
A get data from Thread-1: -1189758355
B get data from Thread-0: 1817494992
B get data from Thread-1: -1189758355
就是线程范围内共享数据,即同一个线程里面这个数据是共享的,线程间是不共享的。
这让我联想到了学习数据库的时候用到的 ThreadLocal,操作数据库需要 connection,如果当前线程中有就拿当前线程中存的 connection,否则就新建一个放到当前线程中,这样就不会出现问题,因为每个线程本身共享了一个 connection,它不是线程间共享的。这也很好理解,这个 connection 肯定不能共享,假设 A 和 B 用户都拿到这个 connection 并开启了事务,现在 A 开始转账了,但是钱还没转好,B 转好了关闭了事务,那么A那边就出问题了。
线程范围内共享数据的问题就总结这么多吧~如果有问题,欢迎指正,我们一起进步!
Java并发基础06. 线程范围内共享数据的更多相关文章
- Java线程与并发库高级应用-线程范围内共享数据ThreadLocal类
1.线程范围内共享变量 1.1 前奏: 使用一个Map来实现线程范围内共享变量 public class ThreadScopeShareData { static Map<Thread, In ...
- Java中的线程--线程范围内共享数据
接着学习Java中的线程,线程范围内的共享数据! 一.线程范围内的数据共享定义 对于相同的程序代码,多个模块在同一个线程中共享一份数据,而在另外线程中运行时又共享另外一份数据. 共享数据中存在的问题, ...
- Java并发基础:线程的创建
线程的创建和管理: 1.应用Thread类显式创建.管理线程 2.应用Executor创建并管理线程. 定义任务: 无返回的任务:实现Runnable接口并编写run()方法. 有响应的任务:实现Ca ...
- Java并发基础04. 线程技术之死锁问题
我们知道,使用 synchronized 关键字可以有效的解决线程同步问题,但是如果不恰当的使用 synchronized 关键字的话也会出问题,即我们所说的死锁.死锁是这样一种情形:多个线程同时被阻 ...
- Java并发基础08. 造成HashMap非线程安全的原因
在前面我的一篇总结(6. 线程范围内共享数据)文章中提到,为了数据能在线程范围内使用,我用了 HashMap 来存储不同线程中的数据,key 为当前线程,value 为当前线程中的数据.我取的时候根据 ...
- Java并发基础概念
Java并发基础概念 线程和进程 线程和进程都能实现并发,在java编程领域,线程是实现并发的主要方式 每个进程都有独立的运行环境,内存空间.进程的通信需要通过,pipline或者socket 线程共 ...
- Java并发基础07. ThreadLocal类以及应用技巧
在前面的文章(6. 线程范围内共享数据)总结了一下,线程范围内的数据共享问题,即定义一个 Map,将当前线程名称和线程中的数据以键值对的形式存到 Map 中,然后在当前线程中使用数据的时候就可以根据当 ...
- java并发基础(五)--- 线程池的使用
第8章介绍的是线程池的使用,直接进入正题. 一.线程饥饿死锁和饱和策略 1.线程饥饿死锁 在线程池中,如果任务依赖其他任务,那么可能产生死锁.举个极端的例子,在单线程的Executor中,如果一个任务 ...
- Java 并发基础
Java 并发基础 标签 : Java基础 线程简述 线程是进程的执行部分,用来完成一定的任务; 线程拥有自己的堆栈,程序计数器和自己的局部变量,但不拥有系统资源, 他与其他线程共享父进程的共享资源及 ...
随机推荐
- 每日一点:git 与 github 区别
絮絮叨叨在前:以前的公司,都用svn 进行代码管理.最近我那程序猿先生真的受不了我,强迫我使用tortoiseGit. 一开始对于 git 和 github 傻傻分不清,干脆自己整理资料,总结一下. ...
- Yuchuan_Linux_C 编程之四动态库(共享库)制作
一.整体大纲 二.共享库的制作 1. 命名规则: lib + 名字 + .so 2. 制作步骤: 1) 生成与位置无关的代码 (生成与位置无关的.o) 2) 将.o打包成共享库(动态库) 3. ...
- php实现post跳转
大家否知道php可以利用header('Location')实现get请求跳转. php利用curl可以实现模拟post请求. 但是却找不到php现成的实现post跳转.那么问题来了,如果有这个需求该 ...
- go package 学习笔记 —— strconv(string与其他基本数据类型(int, float, bool)的转换)
strconv实现了go中基本数据类型与string之间的转换. How to use in go go doc:https://godoc.org/strconv import "strc ...
- 03-influxdb原理
influxdb基本操作 1. influxdb与传统数据库区别 influxdb 传统数据库 database 数据库 measurement 表 points 表里的一行数据 2. 基本原理 2. ...
- 【原创】(求锤得锤的故事)Redis锁从面试连环炮聊到神仙打架。
这是why技术的第38篇原创文章 又到了一周一次的分享时间啦,老规矩,还是先荒腔走板的聊聊生活. 有上面的图是读大学的时候,一次自行车骑行途中队友抓拍的我的照片.拍照的地方,名字叫做牛背山,一个名字很 ...
- Python模块三
collections模块 在内置数据类型(dict.list.set.tuple)的基础上,collections模块还提供了几个额外的数据类型:Counter.deque.defaultdict. ...
- [头脑风暴] 解读Docker Bridge网络模型
背景 这几天在研究Kubernetes, 遇到一个有意思的nodejs镜像:luksa/kubia # 不带端口映射启动容器docker run -it -d luksa/kubia# 连接到默认的B ...
- ReentrantReadWriteLock 源码分析以及 AQS 共享锁 (二)
前言 上一篇讲解了 AQS 的独占锁部分(参看:ReentrantLock 源码分析以及 AQS (一)),这一篇将介绍 AQS 的共享锁,以及基于共享锁实现读写锁分离的 ReentrantReadW ...
- JVM 参数(转)
Herry灬凌夜 转自:https://www.cnblogs.com/wuyx/p/9627542.html 常用的JVM配置参数 一.Trace 跟踪参数 在Eclipse中,如何打开GC的监控 ...