首先先介绍三个性质

可见性

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

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

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

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. 使用 Spring 访问 Hibernate 的方法有哪些?

    我们可以通过两种方式使用 Spring 访问 Hibernate: 1. 使用 Hibernate 模板和回调进行控制反转 2. 扩展 HibernateDAOSupport 并应用 AOP 拦截器节 ...

  2. Leetcode26——删除有序数组中的重复项(双指针法)

    Leetcode26--删除有序数组中的重复项(双指针法) 1. 题目简述 给你一个升序排列的数组 nums ,请你原地 删除重复出现的元素,使每个元素只出现一次 ,返回删除后数组的新长度.元素的相对 ...

  3. c的free 为什么不需要知道大小

    malloc malloc函数在运行时分配内存.它需要以字节为单位的大小并在内存中分配那么多空间.这意味着malloc(50)将在内存中分配50个字节.它返回一个void指针 calloc 与mall ...

  4. 决策树3:基尼指数--Gini index(CART)

    既能做分类,又能做回归.分类:基尼值作为节点分类依据.回归:最小方差作为节点的依据. 节点越不纯,基尼值越大,熵值越大 pi表示在信息熵部分中有介绍,如下图中介绍 方差越小越好. 选择最小的那个0.3 ...

  5. 二十、生成BOM表

  6. HTML5离线存储整理

    前端html部分 //canvas.html <!DOCTYPE html> <html manifest="/test.appcache"> <he ...

  7. react开发教程(三)组件的构建

    什么是组件 组件化就好像我们的电脑装机一样,一个电脑由显示器.主板.内存.显卡.硬盘,键盘,鼠标.... 组件化开发有如下的好处:降低整个系统的耦合度,在保持接口不变的情况下,我们可以替换不同的组件快 ...

  8. js压缩图片到2m以下

    用的canvas.这个问题测试妹子反馈了好几次bug,解决了好多次,虽然用了比较僵硬的办法,但总算最终解决了. 因为php的同事说,页面上的图片要直接调用七牛的接口上传到七牛,所以后端那边不能处理,必 ...

  9. 【Android开发】用WebView访问证书有问题的SSL网页

    Android系统的碎片化很严重,并且手机日期不正确.手机根证书异常.com.google.android.webview BUG等各种原因,都会导致WebViewClient无法访问HTTPS站点. ...

  10. 93. 复原 IP 地址

    做题思路or感想 这种字符串切割的问题都可以用回溯法来解决 递归三部曲: 递归参数 因为要切割字符串,所以要用一个startIndex来控制子串的开头位置,即是会切割出一个范围是[startIndex ...