单例模式

最经典、简单的单例模式

public class Xttblog {
    private static Xttblog instance = new Xttblog();
    private Xttblog() {}
    public static Xttblog getInstance() {
        return instance;
    }
}

这种写法无论在单线程还是多线程环境下都不会出现任何安全性问题。但是,这种实现方式有一个缺点:无论这个单例是否被使用,都会在内存中创建一个这样的单例。所以出现了后续的懒加载实现方式。

懒加载单例模式

public class Xttblog {
    private static Xttblog instance;
    private Xttblog() {}
    public static Xttblog getInstance() {
        if (instance == null) {  // 1
            instance = new Xttblog();
        }
        return instance;
    }
}

懒加载即使用单例的时候才初始化。但是,这种实现方式有一个明显的缺点:当在多线程环境下,多个线程同时运行到代码 1 处时,instance 为 null,这几个线程都会创建自己的单例,而不是使用的同一个单例对象。

懒加载加锁单例模式

public class Xttblog {
    private static Xttblog instance;
    private Xttblog() {}
    public static synchronized Xttblog getInstance() {
        if (instance == null) {
            instance = new Xttblog();
        }
        return instance;
    }
}

方法加锁的实现方式自然能保证多线程环境下的安全性,但是方法加锁的方式会严重影响性能。
接下来考虑细粒度的加锁方式——代码块加锁:

public class Xttblog {
    private static Xttblog instance;
    private Xttblog() {}
    public static Xttblog getInstance() {
        if (instance == null) { // 1
            synchronized (Xttblog.class) { 
                instance = new Xttblog();
            }
        }
        return instance;
    }
}

但是这种细粒度的加锁方式并不能保证多线程环境下的安全性。举例说明:A,B 两个线程同时运行到代码 1 处,接下来两个线程会竞争 Xttblog 类锁。假如 A 线程获得了锁,A 线程继续执行,直到 A 线程创建一个 Xttblog 实例对象,A 线程释放锁。这时 B 线程获取到锁,依然是继续执行,此时 B 线程仍然会创建一个 Xttblog 实例对象。A,B 两个线程就创建了两个不同的 Xttblog 实例对象。

DCL(Double Check Lock)单例模式

public class Xttblog {
    private static volatile Xttblog instance;
    private Xttblog() {}
    public static Xttblog getInstance() {
        if (instance == null) { // 1
            synchronized (Xttblog.class) { 
                if (instance == null) { // 2
                    instance = new Xttblog();
                }
            }
        }
        return instance;
    }
}

volatile关键字的作用:

Java对象的创建不是原子性操作,所以有指令重排序的可能。为了禁止指令重排序,所以要引入 volatile。
所以 volatile 在 DCL 单例中不是使用它的线程可见性,而是禁止指令重排序。

引申1:volatile如何做到的禁止指令重排序?

内存屏障:内存屏障其实就是一个CPU指令,在硬件层面上来说可以扥为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。
主要有两个作用:
(1)阻止屏障两侧的指令重排序;
(2)强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。

引申2:synchronized关键字对类加锁和对类对象加锁的区别?

  • 对类(.class、静态方法)加锁:这个类产生的对象公用这个静态方法,所以这块内存,N个对象来竞争), 这时候,只要是这个类产生的对象,在调用这个静态方法时都会产生互斥;
  • 对类对象(对象、非静态方法)加锁:给这个对象的内存上锁,注意 只是这块内存,其他同类对象都会有各自的内存锁,这时候在其他一个以上线程中执行该对象的这个同步方法(注意:是该对象)就会产生互斥。
posted @ 2021-06-06 22:09  Abserver  阅读(61)  评论(0)    收藏  举报