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()的作用是让步.它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权:但是,并 ...
随机推荐
- python机器学习(五)回归算法-线性回归
一.线性回归的概念 1.1.定义 线性回归通过一个或者多个自变量与因变量之间之间进行建模的回归分析.其中特点为一个或多个称为回归系数的模型参数的线性组合. 优点:结果易于理解,计算不复杂. 缺点: ...
- Unsafe类初探
Unsafe类是java中非常特别的一个类.它名字就叫做"不安全",提供的操作可以直接读写内存.获得地址偏移值.锁定或释放线程. 通过正常途径是无法获得Unsafe实例的,首先它的 ...
- python3.x 基础一
1.python 格式化print #/usr/bin/env python #-*- coding: utf-8 -*- #Author:jenvid.yang # 连接符号+,可以连接多个变量或者 ...
- MYSQL的DOUBLE WRITE双写
期待未来超高速大容量的固态硬盘普及时,只需要CHECKPOINT,而不再需要各种各样的BUFFER,CACHE了 DOUBLE WRITE 在InnoDB将BP中的Dirty Page刷(flush) ...
- 关于Android的真机测试
步骤: 1.开启手机USB调试 2.数据线连接手机和电脑 3.eclipse需要重启 4.在eclipse的run里面的runconfig...里面设置为启动时总是提醒开发者选择 具体事例: 我的手机 ...
- poj2823单调队列认知
Sliding Window Time Limit: 12000MS Memory Limit: 65536K Total Submissions: 62930 Accepted: 17963 ...
- LightOJ1236
题目大意: 给你一个 n,请你找出共有多少对(i,j)满足 lcm(i,j) = n (i<=j) . 解题思路: 我们利用算术基本定理将 n,i,j 进行分解: n = P1a1 * P2a2 ...
- Gunicorn+Nginx+Flask项目部署
安装python3.6 1)前往用户根目录 >: cd ~ 2)下载 或 上传 Python3.6.7 >: wget https://www.python.org/ftp/python/ ...
- RabbitMQ镜像集群搭建
RabbitMQ 官网 https://www.rabbitmq.com/ 小编使用的系统环境是CentOS7.4 系统 IP hostname CentOS7.4 1.1.1.1 hostname0 ...
- CVE-2019-7238 poc
from requests.packages.urllib3.exceptions import InsecureRequestWarning import urllib3 import reques ...