Java 线程安全的单例写法及volatile、原子性、可见性

开发中常用到的单例设计,自己总结一下
单例设计模式分为饿汉式和懒汉式,主要作用是:保证类在内存中对象的唯一性(只有一个对象)
饿汉式是空间换时间;懒汉式是时间换空间

1、饿汉式

/** 
 * 饿汉式,线程安全 但效率比较低 
 * 1.私有构造
 * 2.创建本类对象
 * 3.对外提供公共的访问方法
 */  
public class Person{  
    // 定义一个私有的构造方法
    private Person() {  
    }  

    // 创建本类对象
    private static Person instance = new Person();  

    // 静态方法 返回该类的实例(外部调用的统一入口)
    public static Person getInstance() {  
        return instance;  
    }  

}

为什么是空间换时间呢?因为这个person类对象随着类的加载就已经创建了,(占用了内存空间)当多线程访问时无需 if 判断,可以直接返会创建的这个instance 实例对象——空间换时间

2、懒汉式

/**  
 1. 懒汉式 常用模式
 2. 线程安全  并且效率高  
 3.  
 */  
public class Person{ 

    // 定义一个私有构造方法
    private Person() { 

    }   
    //定义一个静态私有变量(不初始化,使用volatile保证了多线程访问时instance变量的可见性,避免了instance初始化时其他变量属性还没赋值完时,被另外线程调用)
    private static volatile Person instance;  

    //定义一个共有的静态方法,返回该类型实例
    public static Person getInstance() { 
        // 对象实例化时与否判断(不使用同步代码块,instance不等于null时,直接返回对象,提高运行效率)
        if (instance == null) {
            //同步代码块(对象未初始化时,使用同步代码块,保证多线程访问时对象在第一次创建后,不再重复被创建)
            synchronized (Person.class) {
                //未初始化,则初始instance变量
                if (instance == null) {
                    instance = new Person ();   
                }   
            }   
        }   
        return instance;   
    }   
}
  1. 懒汉式为什么叫时间换空间呢,就是因为在多线程并发情况下,创建实例的过程加锁和if判断防止创建多个对象(不加锁可能导致在多个线程同时判断instance == null时,都创建了新的对象)消耗了时间
  2. volatile 关键字 作用:1、保证内存可见性;2、防止指令重排。并不保证操作的原子性
  3. Java程序创建一个实例的过程 。正常过程:(1)分配内存空间; (2)初始化对象;(3)将内存空间的地址值给对应的引用。
  4. 指令重排序 是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。编译器、处理器也遵循这样一个目标。注意是单线程。多线程的情况下指令重排序就会给程序员带来问题。包括编译器重排序和运行时重排序。
  5. 原子性 :只有当变量的值和自身上一个值无关时对该变量的操作才是原子级别的
  6. 重排序后创建实例的过程 (1)分配内存空间; (2)将内存空间的地址值给对应的引用;(3)初始化对象;此时在判断变量时 第一个线程 instance == null 时new 对象(3步骤),同时第二个线程又进来了,走到第二步,引用==null 重新创建对象。使用 volatile 修饰后不会发生重排序,按顺序执行代码
  7. 最后附上一个链接 原子性和可见性说明和一个例子,刨根问底往祖坟上刨 为什么volatile++不是原子性的?

猜你喜欢

转载自blog.csdn.net/guojiayuan002/article/details/80025080