首先先介绍三个性质

可见性

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

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

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

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. IOC 初始化源代码阅读之我见

    由于本人的能力有限,只能说出自己的见解,如有错漏什么的,请大家批评指出.由于代码封装太多,这里只列出了我认为的部分最重要的代码,一些简单的封装代码,不在下面列出.由于代码太过于复杂,在本次博客中,只列 ...

  2. JDBC和桥接模式

    本文参考 网上对于JDBC与桥接模式的理解各有不同,在这片文章里提出的是我个人对于二者的理解,本文参考的其它博文如下: https://blog.csdn.net/paincupid/article/ ...

  3. css两栏布局、圣杯布局、双飞翼布局

    最近几个月一直用vue在写手机端的项目,主要写业务逻辑,在js方面投入的时间和精力也比较多.这两天写页面明显感觉css布局方面的知识有不足,所以复习一下布局方法. 两栏布局 1.浮动 .box1 .l ...

  4. 写了一个web os脚手架

    预览地址在这里:http://thx.github.io/magix-os/项目地址在这里:https://github.com/thx/magix-os 介绍下目录结构 核心目录cores主要是构成 ...

  5. chrome插件one-read开发1:准备

    阅读我的博客文章:chrome插件one-read开发:准备 前言 为啥要做这个,因为我原本想用chrome做一个书签管理的东西,但是很久没有碰过chrome的插件开发了.所以先做一个简单的,来熟悉下 ...

  6. x64 番外篇——保护模式相关

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  7. 访问控制中默认,public,private,protected区别?

    2.继承的访问控制: (比如一个类中的protected成员对于"不同的包中的非子类"是不可见的. 说明:1.任何public的内容可以被从任何地方访问. 2.private的成员 ...

  8. Java中jdk安装与环境变量配置

    Java中jdk安装与环境变量配置 提示:下面是jdk1.7和jdk1.8的百度网盘链接 链接:https://pan.baidu.com/s/1SuHf4KlwpiG1zrf1LLAERQ 提取码: ...

  9. EMS邮箱数据库全局监控设置

    案例任务:监控TestDB01邮箱数据库的所有邮件,监控邮箱为用户"王淑江"的邮箱. 1.EMS全局监控设置 使用PowerShell完成操作:"王淑江"监控T ...

  10. 解决pycharm的爬虫乱码问题(初步了解各种编码格式)

    Ascii码(American Standard Code for Information Interchange,美国信息互换标准代码):最初计算机只在美国使用时,只用8位的字节来组合出256(2的 ...