假设现在有个公共的变量 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. dom节点及对节点的常用操作方法

    dom节点及对节点的常用操作方法 在说dom节点前,先来看看页面的呈现: dom渲染流程:  1.浏览器解析html源码,然后创建一个DOM树. 在DOM树中,每一个HTML标签都有一个对应的节点(元 ...

  2. step(iter)、epoch、batch size之间的关系

    转自:https://blog.csdn.net/wcy23580/article/details/90082221

  3. go 下载qq音乐

    //go下载qq音乐 package main import ( _ "fmt" jsoniter "github.com/json-iterator/go" ...

  4. 一起了解 .Net Foundation 项目 No.16

    .Net 基金会中包含有很多优秀的项目,今天就和笔者一起了解一下其中的一些优秀作品吧. 中文介绍 中文介绍内容翻译自英文介绍,主要采用意译.如与原文存在出入,请以原文为准. Orchard CMS O ...

  5. 02 HDFS 分布式环境实战

    HDFS的主要设计理念 1.存储超大文件 这里的“超大文件”是指几百MB.GB甚至TB级别的文件. 2.最高效的访问模式是 一次写入.多次读取(流式数据访问)3.运行在普通廉价的服务器上 HDFS设计 ...

  6. python学习基础之变量

    变量名只能包含字母.数字和下划线.变量名可以字母或下划线打头,但不能以数字打 头,例如,可将变量命名为message_1,但不能将其命名为1_message. 变量名不能包含空格,但可使用下划线来分隔 ...

  7. 基于osg的python三维程序开发(一)

    背景: osg是一款开源的三维引擎,在过去多年的发展中积累了大量的用户,该引擎基于场景树的管理,使用方法简单.但是对长期使用python作为开发工具的朋友来说, 有一定门槛. 下面的小程序,演示了如何 ...

  8. 等宽字体的妙用-loading 点点点动画

    原理 ch等宽字体 + text-indent 动画负缩进 显示效果如 loading . loading .. loading ... loading . loading .. loading .. ...

  9. Python3对比合并Excel表格

    ##安装模块pip install pandas as pdpip install xlsxwriterpip install openpyxl ##带入模块import pandas as pdim ...

  10. PDIUSBD12管脚简述

    PDIUSBD12管脚简述          PDIUSBD12管脚及简述 PDIUSBD12读写时序图 CS_N是片选信号,当片选信号位低电平时,下面的操作才有效.由于板子上将CS_N接地,所以它一 ...