首先先介绍三个性质

可见性

可见性代表主内存中变量更新,线程中可以及时获得最新的值。

下面例子证明了线程中可见性的问题

由于发现多次执行都要到主内存中取变量,所以会将变量缓存到线程的工作内存,这样当其他线程更新该变量的时候,该线程无法得知,导致该线程会无限的运行下去。

public class test1 {
private static int flag = 1;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
while (flag == 1){ }
},"t1");
t1.start();
Thread.sleep(1000);
flag = 2;
}
}

疑问

当我们在这个死循环中加入一个synchronized关键字的话就会将更新

猜测:synchronized会使更新当前线程的工作内存

public class test1 {
private static int flag = 1;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
while (flag == 1){
synchronized ("1"){ }
}
},"t1");
t1.start();
Thread.sleep(1000);
flag = 2;
}
}

原子性

即多线程中指令执行会出现交错,导致数据读取错误。

比如i++的操作就可以在字节码的层面可以被看成以下操作

9 getstatic #9 <com/zhf/test3/test2.i : I>   获得i
12 iconst_1 将1压入操作数栈
13 isub 将两数相减
14 putstatic #9 <com/zhf/test3/test2.i : I> 将i变量存储

然后在多线程的情况下,会出现以下程序出现非0的结果。

public class test2 {

    private static int i;

    public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (int j = 0; j < 400; j++) {
i++;
}
});
Thread t2 = new Thread(()->{
for (int j = 0; j < 400; j++) {
i--;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}

设计模式---两阶段停止使用volatile

@Slf4j
public class test3 { private Thread monitor;
private volatile boolean flag = false; public static void main(String[] args) {
test3 test3 = new test3(); test3.monitor = new Thread(()->{
while (true){
Thread thread = Thread.currentThread();
if (test3.flag){
log.debug("正在执行后续");
break;
}
try {
log.debug("线程正在执行");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
// 当进程在睡眠过程中被Interrupte()打断此时isInterrupted()为false
// 从而当异常被抓住后会继续执行
// 所以要调用下面方法继续将isInterrupted()置为true
// thread.interrupt();
}
}
}); test3.start();
try {
Thread.sleep(5500);
} catch (InterruptedException e) {
e.printStackTrace();
} test3.stop();
}
public void stop(){
flag = true;
monitor.interrupt();
} public void start(){
monitor.start();
}
}

设计模式---犹豫模式

具体实现,最常见的就是单例模式。

首先是饿汉模式,这里的多线程安全是由JVM保证的,对象是在类加载的加载阶段创建的。

class SingtonHungry{
private static Object object = new Object(); // 饿汉模式
public synchronized Object getObject() {
return object;
}
}

其次就是饿汉模式,最常见的不过就是下面的进行多线程安全的方案。文章后面会对其进行优化。

class SingtonLazy{
private Object object; // 懒汉模式
// 由于这样的话不管有没有创建出对象都要加锁然后才能取对象,性能太差
public synchronized Object getObject() {
if (object == null){
object = new Object();
return object;
}
return object;
}
}

有序性

JVM会对指令进行重排序,其和CPU的流水线操作类似,当需要流水线操作的时候,需要进行优化的时候,就会对CPU指令进行重排序优化。

当操作的顺序变了之后,就会出现问题。可能会导致条件的提前触发等等。

Volatile使用

使用域: Volatile只能在类的静态成员变量或者成员变量上。

volatile标识符能够让线程强制去读主存的该变量的值,保证了线程变量的可见性。

volatile标识符能够让线程去顺序执行该变量的操作,保证了执行变量的语句的有序性

  • 在读取该变量时,会为其添加读屏障。在该读屏障之后的代码不会放在读屏障之前执行。

  • 在写该变量时,会为其添加写屏障。在该写屏障之前的代码不会在屏障之后执行。

所以在volatile的修饰下,能够保证变量的可见性和有序性,但并不能保证其的原子性。

class SingtonLazy{
// 加上volatile的主要目的就是防止在synchronized内的代码指令重排,正常是先构造好对象然后赋对象地址
// 导致object会被首先赋予了地址,导致其不为null,然而构造方法还没有开始构造
// 被其他的线程拿走会出现使用出错。
private static volatile Object object; // 懒汉模式
public static Object getObject() {
if (object != null){
return object;
}else{
// 这里可能出现这里的线程还没有为其进行声明对象,但已经由线程进入了等待锁
// 所以需要在这里来一个为空判断。
synchronized (SingtonLazy.class){
// 这里可能会出现指令重排,所以要加上volatile
if(object == null){
object = new Object();
}
return object;
}
}
}
}

实现单例的另外一个方式

public class Singleton {
// 当使用到ObjectHolder才会进行到这个静态内部类的加载,同时才会创建该类
// 也是属于懒汉式
private static class ObjectHolder{
static final Singleton singleton = new Singleton();
} }

synchronized补充

首先在synchronized代码块中,它会保证代码块中的可见性,原子性和有序性。

有序性仅仅是表现在synchronized的执行后最后的结果都是一样的,并不会阻止JVM在其内部进行代码的重排序。就比如上个例子来说,在synchronized代码块中最后代码的执行结果都是一样的,但可能由于其优化,导致其他线程出错。

Volatile的学习的更多相关文章

  1. volatile关键字 学习记录2

    public class VolatileTest2 implements Runnable{ volatile int resource = 0; public static void main(S ...

  2. volatile关键字 学习记录1

    虽然已经工作了半年了...虽然一直是在做web开发....但是平时一直很少使用多线程..... 然后最近一直在看相关知识..所以就有了这篇文章 用例子来说明问题吧 public class Volat ...

  3. C++中函数的默认参数和C语言中volatile的学习

    1.函数默认参数 1 int func(int a,int b=10) 2 { 3 return a*b; 4 } 5 6 int main() 7 { 8 int c=func(2); 9 cout ...

  4. volatile关键字学习

    volatile关键字在实际工作中我用的比较少,可能因为我并不是造轮子的.但是用的少不是你不掌握的借口,还是要创造场景去使用这个关键字,本文将会提供丰富的demo. volatile 发音:英[ˈvɒ ...

  5. 面经手册 · 第14篇《volatile 怎么实现的内存可见?没有 volatile 一定不可见吗?》

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.码场心得 你是个能吃苦的人吗? 从前的能吃苦大多指的体力劳动的苦,但现在的能吃苦已经包括太 ...

  6. C++——volatile关键字的学习

    首先声明一点,本文是关于volatile关键字的学习,学习内容主要是来自一些大牛的网络博客. 一篇是何登成先生的C/C++ Volatile关键词深度剖析(http://hedengcheng.com ...

  7. JAVA多线程基础学习三:volatile关键字

    Java的volatile关键字在JDK源码中经常出现,但是对它的认识只是停留在共享变量上,今天来谈谈volatile关键字. volatile,从字面上说是易变的.不稳定的,事实上,也确实如此,这个 ...

  8. Java多线程学习(三)volatile关键字

    转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79680693 系列文章传送门: Java多线程学习(一)Java多线程入门 Ja ...

  9. 【java并发编程艺术学习】(三)第二章 java并发机制的底层实现原理 学习记录(一) volatile

    章节介绍 这一章节主要学习java并发机制的底层实现原理.主要学习volatile.synchronized和原子操作的实现原理.Java中的大部分容器和框架都依赖于此. Java代码 ==经过编译= ...

随机推荐

  1. AOP——基于AspectJ的注解来实现AOP操作

    1.使用注解方式实现AOP操作 第一步:创建对象 <!-- 创建对象 --> <bean id="book" class="com.bjxb.aop.B ...

  2. C++分布式系统——《开题》

    在下自大二接触编程,大二.大三刻苦涉猎编程相关书籍,自那时起爱上了 C++,C++确实极有魅力,本想从此在C++领域深钻,但是扩展技术的广度在那个算是半只脚踏入编程且已经读完了 C++ 流行书籍的阶段 ...

  3. js技术之截取substring(int,int)

    案例:把所有单词以空格为分割并将首字母转为大写 <!DOCTYPE html><html lang="en"><head> <meta c ...

  4. leedcode_13 罗马数字转整数

    罗马数字包含以下七种字符: I, V, X, L,C,D 和 M. 字符 数值I 1V 5X 10L 50C 100D 500M 1000例如, 罗马数字 2 写做 II ,即为两个并列的 1 .12 ...

  5. linux环境下搭建solr服务器--单机版

    前提需要在安装好jdk和tomcat,本人用的是jdk1.8+tomcat8.5+solr4.10. 第一步:安装linux.jdk.tomcat.(这步都是比较简单的,就不多说了) 第二步:把sol ...

  6. 从零开始画自己的DAG作业依赖图(四)--节点连线优化版

    概述 上个版本简单的连线在一些复杂场景,尤其层级比较多,连线跨层级比较多的情况下,会出现线条会穿过矩形的情况,这一讲就是在这个基础上,去优化这个连线. 场景分析 在下面几种情况下,简单版本的画法已经没 ...

  7. (Math.round(num*100)/100).toFixed(2); 将输入的数字变成保留两位小数

    <input type="number" @input="onInputPrice" @blur="onPrice" data-id= ...

  8. 解决SVG animation 在IE中不起作用

    问题描述 CSS animation没办法解决SVG路径运动的问题,下图路径运动的过程,通过查资料发现所有的IE的版本都不支持SVG animation.在IE中没有水流动的效果. 主要代码 < ...

  9. Java/C++实现备忘录模式--撤销操作

    改进课堂上的"用户信息操作撤销"实例,使得系统可以实现多次撤销(可以使用HashMap.ArrayList等集合数据结构实现). 类图: Java代码: import java.u ...

  10. Hive进行数据统计时报错:org.apache.hadoop.mapreduce.v2.app.MRAppMaster: Error starting MRAppMaster

    报错详情: 2020-04-09 22:56:58,827 ERROR [Listener at 0.0.0.0/45871] org.apache.hadoop.mapreduce.v2.app.M ...