JUC并发编程学习笔记(十六)Volatile
Volatile
保证可见性
private volatile static Integer num = 0;
使用了volatile关键字,即可保证它本身可被其他线程的工作内存感知,即变化时也会被同步变化。
不保证原子性
原子性:不可分割
线程A在执行任务时是不可被打扰的,也不能被分割,要么同时成功,要么同时失败。
package org.example.tvolatile;
public class VDemo02 {
//synchronized保证原子性,每次只有一条线程执行,所以结果准确
//volatile不保证原子性,虽然也是同步机制,但是结果不准确
private volatile static int num = 0;
public static void add(){
num++;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){//主线程和GC线程
Thread.yield();//让主线程礼让
}
System.out.println(num);
}
}

每次结果也不一样。
如果不加Lock加synchronized,怎么保证原子性?
要了解为什么一个num++都不能保证原子性,首先我们需要查看到他编译好的class字节码文件,找到target,并且右键选择打开外部的文件,找到对应的class文件,再通过javap -c命令反编译查看字节码文件。




查看到字节码文件后可以看到从底层看,其实num++这个操作再多线程下并不安全,有获取和写回这两个操作

那么又回到了这个问题,在volatile中如何保证原子性。打开jdk帮助文档,我们能找到原子性的一些数据类型


在volatile需要保证原子性操作的时候使用原子类来解决原子问题。
原子类为什么高级
原子类的包装类底层使用的是CAS操作。
package org.example.tvolatile;
import java.util.concurrent.atomic.AtomicInteger;
public class VDemo02 {
//synchronized保证原子性,每次只有一条线程执行,所以结果准确
//volatile不保证原子性,虽然也是同步机制,但是结果不准确
private volatile static AtomicInteger num = new AtomicInteger(0);
public static void add(){
num.getAndIncrement();//AtomicInteger+1的方法,并不是简单的+1操作,方法:CAS
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){//主线程和GC线程
Thread.yield();//让主线程礼让
}
System.out.println(num);
}
}
这些类的底层都直接调用c++和操作系统挂钩!在内存中修改值。Unsafe类是一个很特殊的存在!Unsafe类的所有方法都是native方法,调用c++底层
native是与C++联合开发的时候用的!java自己开发不用的!
禁止指令重排
什么是指令重排:你写的程序并不是按照你写的那样去执行的
源代码-----编译器优化的指令重排--------指令并行也可能会指令重排--------内存系统也会重排--------->执行
int x = 1;//1
int y = 2;//2
x = x+5;//3
y = x*x;//4
我们所期望的执行顺序:1、2、3、4;但是如果按照1、3、2、4或者2、4、1、3的顺序执行也是能够顺利运行的,计算机执行时可能对对我们所期望执行顺序的程序进行指令重排,结果是正确的但是对运行顺序有所不同
但是指令重排后的顺序不可能是4、3、2、1或者其它不符合逻辑的顺序。因为处理器在进行指令重拍的过程中会考虑数据之间的依赖性
可能造成影响的结果:四个初始值都为零
| 线程A | 线程B |
|---|---|
| x = a; | y = b; |
| b = 1; | a = 2; |
由于两个线程的操作都没有数据依赖性,指令重排就不会考虑顺序问题,可能会导致最终的执行顺序如下
| 线程A | 线程B |
|---|---|
| b = 1; | a = 2; |
| x = a; | y = b; |
多线程下可能导致一些问题
正常结果:x=0;y=0;
指令重排导致的诡异结果:x=2;y=1;
这是一种概念可能你写1000w行代码都不一定会出现,但是在理论上是一定会参数的。
volatile可以避免指令重排
计算机中存在着一种指令叫做内存屏障,它是一种CPU指令。
作用:
1、保证特定操作的执行顺序(可以让volatile避免指令重排)
2、可以保证某些变量的内存可见性(可以让volatile实现可见性)

Volatile保证可见性、不能保证原子性、由于内存屏障,可以避免指令重排的现象产生!
内存屏障在单例模式中使用的最多!
JUC并发编程学习笔记(十六)Volatile的更多相关文章
- JUC并发编程学习笔记
JUC并发编程学习笔记 狂神JUC并发编程 总的来说还可以,学到一些新知识,但很多是学过的了,深入的部分不多. 线程与进程 进程:一个程序,程序的集合,比如一个音乐播发器,QQ程序等.一个进程往往包含 ...
- Java并发编程学习笔记 深入理解volatile关键字的作用
引言:以前只是看过介绍volatile的文章,对其的理解也只是停留在理论的层面上,由于最近在项目当中用到了关于并发方面的技术,所以下定决心深入研究一下java并发方面的知识.网上关于volatile的 ...
- 并发编程学习笔记(6)----公平锁和ReentrantReadWriteLock使用及原理
(一)公平锁 1.什么是公平锁? 公平锁指的是在某个线程释放锁之后,等待的线程获取锁的策略是以请求获取锁的时间为标准的,即使先请求获取锁的线程先拿到锁. 2.在java中的实现? 在java的并发包中 ...
- Java并发编程学习笔记
Java编程思想,并发编程学习笔记. 一.基本的线程机制 1.定义任务:Runnable接口 线程可以驱动任务,因此需要一种描述任务的方式,这可以由Runnable接口来提供.要想定义任务,只需实现R ...
- 并发编程学习笔记(14)----ThreadPoolExecutor(线程池)的使用及原理
1. 概述 1.1 什么是线程池 与jdbc连接池类似,在创建线程池或销毁线程时,会消耗大量的系统资源,因此在java中提出了线程池的概念,预先创建好固定数量的线程,当有任务需要线程去执行时,不用再去 ...
- 并发编程学习笔记(5)----AbstractQueuedSynchronizer(AQS)原理及使用
(一)什么是AQS? 阅读java文档可以知道,AbstractQueuedSynchronizer是实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量.事件,等等)提供一个框架, ...
- 并发编程学习笔记(3)----synchronized关键字以及单例模式与线程安全问题
再说synchronized关键字之前,我们首先先小小的了解一个概念-内置锁. 什么是内置锁? 在java中,每个java对象都可以用作synchronized关键字的锁,这些锁就被称为内置锁,每个对 ...
- python3.4学习笔记(十六) windows下面安装easy_install和pip教程
python3.4学习笔记(十六) windows下面安装easy_install和pip教程 easy_install和pip都是用来下载安装Python一个公共资源库PyPI的相关资源包的 首先安 ...
- 并发编程学习笔记(15)----Executor框架的使用
Executor执行已提交的 Runnable 任务的对象.此接口提供一种将任务提交与每个任务将如何运行的机制(包括线程使用的细节.调度等)分离开来的方法.通常使用 Executor 而不是显式地创建 ...
- 并发编程学习笔记(13)----ConcurrentLinkedQueue(非阻塞队列)和BlockingQueue(阻塞队列)原理
· 在并发编程中,我们有时候会需要使用到线程安全的队列,而在Java中如果我们需要实现队列可以有两种方式,一种是阻塞式队列.另一种是非阻塞式的队列,阻塞式队列采用锁来实现,而非阻塞式队列则是采用cas ...
随机推荐
- MAUI Blazor Android 输入框软键盘遮挡问题2.0
前言 关于MAUI Blazor Android 输入框软键盘遮挡问题,之前的文章已经有了答案,MAUI Blazor Android 输入框软键盘遮挡问题 但是这个方案一直存在一点小的瑕疵 在小窗模 ...
- mysql:EXPLAIN
推荐阅读原文:EXPLAIN用法和结果分析 语法:EXPLAIN SELECT * FROM t1 使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句 ...
- 代码随想录算法训练营第二天| LeetCode 977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II
977.有序数组的平方 题目链接:https://leetcode.cn/problems/squares-of-a-sorted-array/ 文章讲解:https://programmercarl ...
- 三个编程思想:面向对象编程、面向接口编程、面向过程编程【概念解析系列_1】【C# 基础】
〇.前言 对于 .Net 中的编程思想还是十分重要的,也是编码出高效的程序的基础! 在使用之前了解其本质,那么用起来就游刃有余.下面来简单对比下三个编程思想,看下它们都是什么,它们之间又有什么关系. ...
- IDA函数特征识别自动签名
IDA函数特征识别自动签名 Vc6编译的有些无法识别一些库里面的函数 测试代码 #include <stdio.h> int main() { printf("123456\n& ...
- Mybatis操作数据库流程源码
Java操作数据库需要经过3个大步骤: 获取数据库连接 执行SQL语句 关闭数据库连接 Mybatis将这几个步骤进行了封装,将获取数据库连接的给工作交给了SqlSessionFactory,将执行S ...
- 园子的脱困努力-云厂商合作:领取阿里云免费ECS试用资源,部署Java Web环境,送小礼品
在园子脱困的关键时期,每一笔收入都很重要,一边在会员救园,一边我们要努力把握每一个商务合作机会,争取早日走出困境. 之前园子维持生存的收入主要来自于与云厂商的合作,但去年由于云厂商推广策略的调整,这块 ...
- python flask 简单应用开发
转载请注明出处: Flask 是一个基于 Python 的微型 Web 框架,它提供了一组简洁而强大的工具和库,用于构建 Web 应用程序.Flask 的主要作用是帮助开发者快速搭建轻量级的.灵活的 ...
- torch.nn基础学习教程 | PyTorch nn Basic Tutorial
基于torch.nn搭建神经网络的基础教程大纲: 1. 引言 在我们开始深入探讨torch.nn之前,我们首先需要理解PyTorch及其神经网络库的基础知识.这一部分的内容将帮助你对PyTorch有一 ...
- Spark入门系列视频教程
视频目录: Spark入门| 01 Spark概念架构 Spark入门| 02 Spark集群搭建 Spark入门| 03 Spark Shell算子操作 Spark入门| 04 Spark单词计数 ...