标签:阶段 ati 行操作 轻量级 while 没有 技术 解决问题 不一致
代码比较简单,我就不贴出来了。
子线程t从主内存读取到数据放入其对应的工作内存
将flag的值更改为true,但是这个时候flag的值还没有写会主内存
此时main方法main方法读取到了flag的值为false
当子线程t将flag的值写回去后,失效其他线程对此变量副本
再次对flag进行操作的时候线程会从主内存读取最新的值,放入到工作内存中
总结: volatile保证不同线程对共享变量操作的可见性,也就是说一个线程修改了volatile修饰的变量,当修改写回主内存时,另外一个线程立即看到最新的值。
写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中
而读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据
问题代码示例:
/**
* @author WGR
* @create 2020/12/30 -- 21:10
*/
public class OutOfOrderDemo06 {
// 新建几个静态变量
public static int a = 0 , b = 0;
public static int i = 0 , j = 0;
public static void main(String[] args) throws Exception {
int count = 0;
while(true){
count++;
a = 0 ;
b = 0 ;
i = 0 ;
j = 0 ;
// 定义两个线程。
// 线程A
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
a = 1;
i = b;
}
});
// 线程B
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
b = 1;
j = a;
}
});
t1.start();
t2.start();
t1.join(); // 让t1线程优先执行完毕
t2.join(); // 让t2线程优先执行完毕
// 得到线程执行完毕以后 变量的结果。
System.out.println("第"+count+"次输出结果:i = " +i +" , j = "+j);
if(i == 0 && j == 0){
break;
}
}
}
}
发生了重排序:在线程1和线程2内部的两行代码的实际执行顺序和代码在Java文件中的顺序是不一致的,代码指令并不是严格按照代码顺序执行的,他们的顺序改变了,这样就是发生了重排序,这里颠倒的 是 a = 1 ,i = b 以及j=a , b=1 的顺序,从而发生了指令重排序。直接获取了i = b(0) , j = a(0)的值!显然这个值是不对的。
但是加上volatile关键字就会解决问题。
按照happens-before规则,我们只需要给b加上volatile,那么b之前的写入( a = 3;)将对读取b之后的代码可见,也就是说即使a不加volatile,只要b读取到3,那么b之前的操作(a=3)就一定是可见的,此时就绝对不会出现b=3的时候而读取到a=1了。
happens-before规则可以看我这个面试题:https://www.cnblogs.com/dalianpai/p/14212690.html
写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
读写屏障可以参考这个面试题:https://www.cnblogs.com/dalianpai/p/14162021.html
单例概述
单例模式的适用场景:
单例模式有8种
单例模式我们可以提供出8种写法,有很多时候我们存在饿汉式单例的概念,以及懒汉式单例的概念。
饿汉单例的2种写法
特点:在获取单例对象之前对象已经创建完成了。
/**
目标:饿汉式(静态常量)
步骤:
1.构造器私有。
2.定义一个静态常量保存一个唯一的实例对象(单例)
3.
*/
public class Singleton01 {
// 2.定义一个静态常量保存一个唯一的实例对象(单例)
private static final Singleton01 INSTANCE = new Singleton01();
// 1.构造器私有。
private Singleton01(){
}
// 3.提供一个方法返回单例对象。
public static Singleton01 getInstance(){
return INSTANCE;
}
}
class Test01{
public static void main(String[] args) {
Singleton01 s1 = Singleton01.getInstance();
Singleton01 s2 = Singleton01.getInstance();
System.out.println(s1 == s2);
}
}
/**
目标:饿汉式(静态代码块)
步骤:
1.构造器私有。
2.定义一个静态常量保存一个唯一的实例对象(单例),可以通过静态代码块初始化单例对象。
3.提供一个方法返回单例对象。
*/
public class Singleton02 {
// 2.定义一个静态常量保存一个唯一的实例对象(单例)
private static final Singleton02 INSTANCE ;
static{
INSTANCE = new Singleton02();
}
// 1.构造器私有。
private Singleton02(){
}
// 3.提供一个方法返回单例对象。
public static Singleton02 getInstance(){
return INSTANCE;
}
}
class Test02{
public static void main(String[] args) {
Singleton02 s1 = Singleton02.getInstance();
Singleton02 s2 = Singleton02.getInstance();
System.out.println(s1 == s2);
}
}
懒汉式单例4种写法
特点:在真正需要单例的时候才创建出该对象。在Java程序中,有时候可能需要推迟一些高开销对象的初始化操作,并且只有在使用这些对象的时候才初始化,此时,程序员可能会采用延迟初始化。值得注意的是:要正确的实现线程安全的延迟初始化还是需要一些技巧的,否则很容易出现问题。
/**
目标:懒汉式(线程不安全的写法)。
步骤:
1.构造器私有。
2.定义一个静态的变量存储一个单例对象(定义的时候不初始化该对象)
3.定义一个获取单例的方法,每次返回单例对象的时候先询问是否有对象,有直接返回。
没有就创建一个新的单例对象。
*/
public class Singleton03 {
// 2.定义一个静态的变量存储一个单例对象(定义的时候不初始化该对象)
private static Singleton03 INSTANCE;
// 1.构造器私有。
private Singleton03(){
}
// 3.定义一个获取单例的方法,每次返回单例对象的时候先询问是否有对象,有直接返回。
// 没有就创建一个新的单例对象。
public static Singleton03 getInstance(){
if(INSTANCE == null){
// 说明这是第一次来拿单例对象,需要真正的创建出来!
INSTANCE = new Singleton03();
}
return INSTANCE;
}
}
使用synchronized关键字修饰方法包装线程安全,但性能差多,并发下只能有一个线程正在进入获取单例对象。
/**
目标:懒汉式(线程安全的写法)。
步骤:
1.构造器私有。
2.定义一个静态的变量存储一个单例对象(定义的时候不初始化该对象)
3.定义一个获取单例的方法,每次返回单例对象的时候先询问是否有对象,有直接返回。
没有就创建一个新的单例对象。
4.为获取单例的方法加锁:用synchronized
*/
public class Singleton04 {
// 2.定义一个静态的变量存储一个单例对象(定义的时候不初始化该对象)
private static Singleton04 INSTANCE;
// 1.构造器私有。
private Singleton04(){
}
// 3.定义一个获取单例的方法,每次返回单例对象的时候先询问是否有对象,有直接返回。
// 没有就创建一个新的单例对象。
// 懒汉式线程安全的写法:线程A , 线程B.
public synchronized static Singleton04 getInstance(){
if(INSTANCE == null){
// 说明这是第一次来拿单例对象,需要真正的创建出来!
INSTANCE = new Singleton04();
}
return INSTANCE;
}
}
特点:是一种优化后的似乎线程安全的机制。
/**
目标:懒汉式(线程不安全)
步骤:
1.构造器私有。
2.定义一个静态变量存储一个单例对象。
3.提供一个方法返回一个单例对象。
*/
public class Singleton05 {
// 2.定义一个静态变量存储一个单例对象。
private static Singleton05 INSTANCE ;
// 1.构造器私有
private Singleton05(){
}
// 3.返回一个单例对象
public static Singleton05 getInstance(){
// 判断单例对象的变量是否为null
if(INSTANCE == null){
// 很多个线程执行到这里来:A , B
synchronized (Singleton05.class){
INSTANCE = new Singleton05();
}
}
return INSTANCE;
}
}
/**
目标:双重检查机制,以及使用volatile修饰(最好,最安全的方式,推荐写法)
步骤:
1.构造器私有。
2.提供了一个静态变量用于存储一个单例对象。
3.提供一个方法进行双重检查机制返回单例对象。
4.必须使用volatile修饰静态的变量。?
双重检查的优点:线程安全,延迟加载,效率较高!!
*/
public class Singleton06 {
// 2.提供了一个静态变量用于存储一个单例对象。
private volatile static Singleton06 INSTANCE;
// 1.构造器私有。
private Singleton06(){
}
// 3.提供一个方法进行双重检查机制返回单例对象。
public static Singleton06 getInstance(){
// 第一次检查:判断单例对象的变量是否为null
if(INSTANCE == null ){
// A , B
synchronized (Singleton06.class){
// 第二次检查:判断单例对象的变量是否为null
if(INSTANCE == null){
INSTANCE = new Singleton06();
}
}
}
return INSTANCE;
}
}
引入:JVM在类初始化阶段(即在Class被加载后,且线程使用之前),会执行类的初始化。在执行类的初始化期间,JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。基于这个特性,可以实现另一种线程安全的延迟初始化方案
/**
目标:基于类的初始化实现延迟加载和线程安全的单例设计。
步骤:
1.构造器私有。
2.提供一个静态内部类,里面提供一个常量存储一个单例对象。
3.提供一个方法返回静态内部类中的单例对象。
*/
public class Singleton07 {
// 1.构造器私有。
private Singleton07(){
}
// 2.提供一个静态内部类,里面提供一个常量存储一个单例对象。
private static class Inner{
private static final Singleton07 INSTANCE = new Singleton07();
}
// .提供一个方法返回静态内部类中的单例对象。
// 线程A , 线程B
public static Singleton07 getInstance(){
return Inner.INSTANCE;
}
}
/**
目标:枚举实现单例。
引入:枚举实际上是一种多例的模式。如果我们直接定义一个实例就相当于是单例了。
*/
public enum Singleton08 {
INSTANCE;
}
赋值操作,volatile不适合做a++等操作。 如果一个共享变量自始至终只被各个线程赋值,而没有其他的操作,那么就可以用volatile来代替synchronized或者代替原子变量,因为赋值自身是有原子性的,而volatile又保证了可见性,所以就足以保证线程安全。
触发器,按照volatile的可见性和禁止重排序以及happens-before规则,volatile可以作为刷新之前变量的触发器。我们可以将某个变量设置为volatile修饰,其他线程一旦发现该变量修改的值后,触发获取到的该变量之前的操作都将是最新的且可见。
标签:阶段 ati 行操作 轻量级 while 没有 技术 解决问题 不一致
原文地址:https://www.cnblogs.com/dalianpai/p/14213706.html