Java 多线程基础(四)线程安全
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 多线程基础(四)线程安全的更多相关文章
- Java多线程基础知识总结
2016-07-18 15:40:51 Java 多线程基础 1. 线程和进程 1.1 进程的概念 进程是表示资源分配的基本单位,又是调度运行的基本单位.例如,用户运行自己的程序,系统就创建一个进程, ...
- “全栈2019”Java多线程第四章:设置和获取线程名称
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- Java 多线程基础(五)线程同步
Java 多线程基础(五)线程同步 当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题. 要解决上述多线程并发访问一个资源的安全性问题,Java中提供了同步机制 ...
- Java 多线程基础(六)线程等待与唤醒
Java 多线程基础(六)线程等待与唤醒 遇到这样一个场景,当某线程里面的逻辑需要等待异步处理结果返回后才能继续执行.或者说想要把一个异步的操作封装成一个同步的过程.这里就用到了线程等待唤醒机制. 一 ...
- Java 多线程基础(十)interrupt()和线程终止方式
Java 多线程基础(十)interrupt()和线程终止方式 一.interrupt() 介绍 interrupt() 定义在 Thread 类中,作用是中断本线程. 本线程中断自己是被允许的:其它 ...
- Java多线程基础:进程和线程之由来
转载: Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够 ...
- 1、Java多线程基础:进程和线程之由来
Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够融会贯通 ...
- Java 多线程基础(七)线程休眠 sleep
Java 多线程基础(七)线程休眠 sleep 一.线程休眠 sleep sleep() 方法定义在Thread.java中,是 static 修饰的静态方法.sleep() 的作用是让当前线程休眠, ...
- Java 多线程基础(八)线程让步
Java 多线程基础(八)线程让步 yield 一.yield 介绍 yield()的作用是让步.它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权:但是,并 ...
随机推荐
- 复习MintUI
一.表单----复选框列表 1.<mt-checklist title="标题" options="['a','b','c']" #选项列表 v-mode ...
- Django启动
Django启动 (一)CMD中创建启动: 1.配置好django-admin.exe环境变量,切换到项目文件夹路径 切换磁盘:>>>E: 显示文件列表:>>>di ...
- Redis学习笔记(十三) 复制(下)
上一篇写了Redis复制功能的简单应用,下面我们看下Redis复制功能的实现过程.下面基本上是理论部分,枯燥乏味,但希望大家能看看,毕竟知识不都是感兴趣的.耐得住寂寞,经得起诱惑,方能守得住繁华 ~. ...
- stm32实现DMX512协议发送与接收(非标)
最近把玩了一下485,期间也接触了dmx512通信协议,该协议主要用于各种舞台灯光的控制当中,进而实现各种光效以及色彩变化.根据标准的512协议,其物理连接与传统上的RS485是完全一致的,并没有什么 ...
- 启动独立的tomcat服务器,没有自动创建ServletContext,对Context生命周期的监听失败
1.可能web.xml文件里对ContextListener没有进行配置 2.web.xml文件有关对ContextListener的配置,出现了错误的单词拼写问题 比如 <listener&g ...
- Node.js NPM Tutorial
Node.js NPM Tutorial – How to Get Started with NPM? NPM is the core of any application that is devel ...
- DBCP连接池和事物
工具类案例 public static final String DRIVER = "com.mysql.jdbc.Driver"; public static final Str ...
- vue生命周期函数2
转载:http://blog.csdn.net/qq_15766181/article/details/73549933 钩子就好像是把人的出生到死亡分成一个个阶段,你肯定是在出生阶段起名字,而不会在 ...
- Android_四大组件之BroadcastReceiver
一.概述 BroadcastReceiver是广播接收器,接收来自 系统或应用发出的广播信息 并进行相应的逻辑处理. 自定义BroadcastReceiver只需继承android.content.B ...
- Linux、Ubuntu、CentOS安装和配置zsh
目录 01 zsh的安装 02 配置zsh 2.1 安装oh-my-zsh 2.2 查看oh-my-zsh目录 2.3 oh-my-zsh 插件的管理 2.3.1 添加插件 2.3.2 zsh-aut ...