Java 多线程基础(四)线程安全

在多线程环境下,如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。 在了解线程安全之前,先来说一下Java的内存模型 JMM ,先了解多线程是如何工作的。

一、JMM(Java Memory Model)

JMM是一种基于计算机内存模型(定义了共享内存系统中多线程程序读写操作行为的规范),屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。保证共享内存的原子性、可见性、有序性。

(一)、多线程的执行环境

上图描述了一个多线程执行场景。线程 A 和线程 B 分别对主内存的变量进行读写操作。其中主内存中的变量为共享变量,也就是说此变量只此一份,多个线程间共享。但是线程不能直接读写主内存的共享变量,每个线程都有自己的工作内存,线程需要读写主内存的共享变量时需要先将该变量拷贝一份副本到自己的工作内存,然后在自己的工作内存中对该变量进行所有操作,线程工作内存对变量副本完成操作之后需要将结果同步至主内存。

线程的工作内存是线程私有内存,线程间无法互相访问对方的工作内存。

(二)、多线程执行的流程

从上图可以看出,线程A执行完x++后,将数据同步到主内存中,线程B再访问时,是线程A同步后的数据,所以保证了线程安全。那么,线程工作内存怎么知道什么时候又是怎样将数据同步到主内存呢?这是因为有了 JMM。JMM 规定了何时以及如何做线程工作内存与主内存之间的数据同步。

二、多线程中的核心概念

(一)、原子性:对共享内存的操作必须是要么全部执行直到执行结束,且中间过程不能被任何外部因素打断,要么就不执行。

(二)、可见性:多线程操作共享内存时,执行结果能够及时的同步到共享内存,确保其他线程对此结果及时可见。

(三)、有序性:程序的执行顺序按照代码顺序执行,在单线程环境下,程序的执行都是有序的,但是在多线程环境下,JMM 为了性能优化,编译器和处理器会对指令进行重排,程序的执行会变成无序。

三、线程安全

从上面可以知道,主内存中的变量是共享的,所有线程都可以访问读写,而线程工作内存又是线程私有的,线程间不可互相访问。那在多线程场景下,图上的线程 A 和线程 B 同时来操做共享内存里的同一个变量,那么主内存内的此变量数据就会被破坏。也就是说主内存内的此变量不是线程安全的。我们来看个代码帮助理解。

public class ThreadDemo {
private int x = 0;
public void count() {
x++;
}
public void test() {
// 线程A
new Thread(() ->{
for (int i = 0; i < 1000000; i++) {
count();
}
System.out.println(Thread.currentThread().getName() + " : " + x);
}).start();
// 线程B
new Thread(() ->{
for (int i = 0; i < 1000000; i++) {
count();
}
System.out.println(Thread.currentThread().getName() + " : " + x);
}).start(); }
public static void main(String[] args) {
new ThreadDemo().test();
}
}
// 输出结果
Thread-0 : 1033950
Thread-1 : 1944281

上面代码开启两个线程,分别调用 count() 执行 x++ 的操作,理论上,两个线程执行完成后,应该有一个线程会输出 x = 2000000,但是从结果上看,和预期的结果不同,出现这样的结果的原因也就是我们上面所说的,在多线程环境下,我们主内存的 x 变量的数据被破坏了。执行流程如下图:

从上图可以看到,线程A和线程B共同访问同一资源 x , 其中线程A在对资源进行写操作中途,线程B对这个线程A写了一半的资源进行读写操作,导致资源数据 x 出现了错误。为了避免出现这种情况,Java 提供了线程同步的方法来解决这个问题。线程同步内容可以参考:Java 多线程基础(五)线程同步

四、总结

(一)、出现线程安全问题的原因

在多个线程并发环境下,多个线程共同访问同一共享内存资源时,其中一个线程对资源进行写操作的中途(写入已经开始,但还没 结束),其他线程对这个写了一半的资源进行了读操作,或者对这个写了一半的资源进行了写操作,导致此资源出现数据错误。

(二)、如何避免线程安全问题?

1、保证共享资源在同一时间只能由一个线程进行操作(原子性,有序性)。

2、将线程操作的结果及时刷新,保证其他线程可以立即获取到修改后的最新数据(可见性)。

Java 多线程基础(四)线程安全的更多相关文章

  1. Java多线程基础知识总结

    2016-07-18 15:40:51 Java 多线程基础 1. 线程和进程 1.1 进程的概念 进程是表示资源分配的基本单位,又是调度运行的基本单位.例如,用户运行自己的程序,系统就创建一个进程, ...

  2. “全栈2019”Java多线程第四章:设置和获取线程名称

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  3. Java 多线程基础(五)线程同步

    Java 多线程基础(五)线程同步 当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题. 要解决上述多线程并发访问一个资源的安全性问题,Java中提供了同步机制 ...

  4. Java 多线程基础(六)线程等待与唤醒

    Java 多线程基础(六)线程等待与唤醒 遇到这样一个场景,当某线程里面的逻辑需要等待异步处理结果返回后才能继续执行.或者说想要把一个异步的操作封装成一个同步的过程.这里就用到了线程等待唤醒机制. 一 ...

  5. Java 多线程基础(十)interrupt()和线程终止方式

    Java 多线程基础(十)interrupt()和线程终止方式 一.interrupt() 介绍 interrupt() 定义在 Thread 类中,作用是中断本线程. 本线程中断自己是被允许的:其它 ...

  6. Java多线程基础:进程和线程之由来

    转载: Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够 ...

  7. 1、Java多线程基础:进程和线程之由来

    Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够融会贯通 ...

  8. Java 多线程基础(七)线程休眠 sleep

    Java 多线程基础(七)线程休眠 sleep 一.线程休眠 sleep sleep() 方法定义在Thread.java中,是 static 修饰的静态方法.sleep() 的作用是让当前线程休眠, ...

  9. Java 多线程基础(八)线程让步

    Java 多线程基础(八)线程让步 yield 一.yield 介绍 yield()的作用是让步.它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权:但是,并 ...

随机推荐

  1. 【Redis】String应用场景

    单值缓存 SET key value GET key 对象缓存 SET user: value(json格式数据) MSET user::name value1 user::balance value ...

  2. 模板:分页JSP(结合Servlet)

    DAO类(后续无需改变) package dao; import java.sql.Connection; import java.sql.PreparedStatement; import java ...

  3. Poj1753 翻转棋子

    这个题就是用枚举举遍所有情况,然后一个一个深搜看看是不是符合条件,符合条件直接退出,不符合则继续, 由于表格只有16个所以可以得知最多的步数只能是16,所以可以根据步数从0到16依次枚举, 第一个符合 ...

  4. Java内存虚拟机理解

        对于Java程序员,在虚拟机自动内存管理机制的帮助下,不需要再为每一个操作写配对的释放资源操作,不容易出现内存泄露和内存溢出问题.加深对Java虚拟机的理解,有助于在发现问题时精准定位问题,排 ...

  5. Python 绘制全球疫情地图

    国内疫情得到控制后,我就没怎么再关心过疫情,最近看到一条新闻,全球疫情累计确诊人数已经突破 500w 大关,看到这个数字我还是有点吃惊的. 思来想去,还是写一篇全球疫情的分析的文章,本文包括网络爬虫. ...

  6. 实验二:Linux系统简单文件操作命令

    項目 内容 这个作业属于哪个课程() 课程链接 这个作业的要求在哪里 实验要求 学号-姓名 17043133-木腾飞 作业学习目标 学习在Linux系统终端下进行命令行操作: 学习掌握常用命令行操作并 ...

  7. 面试中很值得聊的二叉树遍历方法——Morris遍历

    Morri遍历 通过利用空闲指针的方式,来节省空间.时间复杂度O(N),额外空间复杂度O(1).普通的非递归和递归方法的额外空间和树的高度有关,递归的过程涉及到系统压栈,非递归需要自己申请栈空间,都具 ...

  8. [JavaWeb基础] 023.线程安全(二)

    上一篇我们讲解了线程安全的问题,那么要解决线程安全的问题,我们就必须用到线程同步,保证线程之间不互相影响而产生脏数据,下面我们来讲讲具体的实现吧. 首先我们看下例子,我们有个Outputter类,用于 ...

  9. jchdl - GSL实例 - Mux4(使用WireVec简化输入线声明)

    https://mp.weixin.qq.com/s/yJx_dV6ScUStJtPWVuD38w 原理图 ​​ 参考链接 https://github.com/wjcdx/jchdl/blob/ma ...

  10. (Java实现) 拦截导弹

    1260:[例9.4]拦截导弹(Noip1999) 时间限制: 1000 ms 内存限制: 65536 KB 提交数: 4063 通过数: 1477 [题目描述] 某国为了防御敌国的导弹袭击,发展出一 ...