假设现在有个公共的变量 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. 线程范围内共享数据的更多相关文章

  1. Java线程与并发库高级应用-线程范围内共享数据ThreadLocal类

    1.线程范围内共享变量 1.1 前奏: 使用一个Map来实现线程范围内共享变量 public class ThreadScopeShareData { static Map<Thread, In ...

  2. Java中的线程--线程范围内共享数据

    接着学习Java中的线程,线程范围内的共享数据! 一.线程范围内的数据共享定义 对于相同的程序代码,多个模块在同一个线程中共享一份数据,而在另外线程中运行时又共享另外一份数据. 共享数据中存在的问题, ...

  3. Java并发基础:线程的创建

    线程的创建和管理: 1.应用Thread类显式创建.管理线程 2.应用Executor创建并管理线程. 定义任务: 无返回的任务:实现Runnable接口并编写run()方法. 有响应的任务:实现Ca ...

  4. Java并发基础04. 线程技术之死锁问题

    我们知道,使用 synchronized 关键字可以有效的解决线程同步问题,但是如果不恰当的使用 synchronized 关键字的话也会出问题,即我们所说的死锁.死锁是这样一种情形:多个线程同时被阻 ...

  5. Java并发基础08. 造成HashMap非线程安全的原因

    在前面我的一篇总结(6. 线程范围内共享数据)文章中提到,为了数据能在线程范围内使用,我用了 HashMap 来存储不同线程中的数据,key 为当前线程,value 为当前线程中的数据.我取的时候根据 ...

  6. Java并发基础概念

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

  7. Java并发基础07. ThreadLocal类以及应用技巧

    在前面的文章(6. 线程范围内共享数据)总结了一下,线程范围内的数据共享问题,即定义一个 Map,将当前线程名称和线程中的数据以键值对的形式存到 Map 中,然后在当前线程中使用数据的时候就可以根据当 ...

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

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

  9. Java 并发基础

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

随机推荐

  1. js中(event)事件对象

    事件对象 • 什么是事件对象? • 就是当你触发了一个事件以后,对该事件的一些描述信息 • 例如: ° 你触发一个点击事件的时候,你点在哪个位置了,坐标是多少 ° 你触发一个键盘事件的时候,你按的是哪 ...

  2. 2653 区间xor

    前言 这个题目在我之前那篇c++位运算的的随笔中提到过. 有兴趣的话去看看吧! 飞机场:https://www.cnblogs.com/laoguantongxiegogofs/p/12444517. ...

  3. 轻装上阵Flink--在IDEA上开发基于Flink的实时数据流程序

    前言 本文介绍如何在IDEA上快速开发基于Flink框架的DataStream程序.先直接上手! 环境清单 案例是在win7运行.安装VirtualBox,在VirtualBox上安装Centos操作 ...

  4. element中的树形组件,如何获取父级菜单的id

    一般多选的树形组件,使用getCheckedNodes()方法只能获取到本级的菜单id,只有在子菜单全部选中的情况下才会选中上级.但我们想要不全选中子级的情况下也要获取它的上级,甚至上上级等,怎么办呢 ...

  5. 【Win10】我们无法更新系统保留的分区

      前言 笔者是一个萌新,这个方案也是慢慢摸索出来的,有更好的方案欢迎大家提出 前段时间用公司电脑发现win10新版本还行,回家升级自己的电脑却提示“我们无法更新系统保留的分区”.(O_o)?? 笔者 ...

  6. oracle去除重复数据与oracle分页

    一.去除oracle中重复数据,可以使用rowid列,rowid列是一个伪列,该列在数据库中灭一个表中都有,但是我们查询数据库的时候,默认都没有给我们返回这一列,这一列用来区分数据库中的每一行时间,可 ...

  7. atomic的底层实现

    atomic操作 在编程过程中我们经常会使用到原子操作,这种操作即不想互斥锁那样耗时,又可以保证对变量操作的原子性,常见的原子操作有fetch_add.load.increment等. 而对于atom ...

  8. linux redis安装 5.0.2

    参看:https://www.cnblogs.com/limit1/p/9045183.html 1.获取redis资源 wget http://download.redis.io/releases/ ...

  9. Java基础 - 原码、反码、补码

    目录 机器数 真值 原码 反码 补码 为什么使用原码. 反码. 补码 机器数 所有数字在计算机底层都是以二进制形式存在的.它的表现形式叫做机器数,这个数有正负之分,最高位为符号位.0 表示正数, 1 ...

  10. Java爬取丁香医生疫情数据并存储至数据库

    1.通过页面的url获取html代码 // 根URL private static String httpRequset(String requesturl) throws IOException { ...