饿汉式单例
//单例模式,一般用于比较大,复杂的对象,只初始化一次,应该还有一个private的构造函数,使得不能用new来实例化对象,只能调用getInstance方法来得到对象,
// 而getInstance保证了每次调用都返回相同的对象。
//饿汉式单例
//饿汉式就是一上来就把对象加载了,不管是否被使用,饿汉式会造成资源浪费
public class Hungry {
//单例模式最重要的是构造器私有化
private Hungry(){
}
private final static Hungry HUNGRY=new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
懒汉式单例
//懒汉式单例
public class Lazy {
private Lazy(){
System.out.println(Thread.currentThread().getName()+"ok");
}
private static Lazy lazy;
public static Lazy getInstance(){
if (lazy == null) {
lazy = new Lazy();
}
return lazy;
}
}
//模拟多线程测试
public static void main(String[] args) {
for(int i=0;i<10;i++){
new Thread(()->{
Lazy.getInstance();
}).start();
}
}
打印输出了十条语句,出现了线程安全问题!
多线程情况下,多个线程同时执行到 if(lazy==null)语句,创建多个引用对象。
解决方法,双重检验锁
//懒汉式单例
public class Lazy {
private Lazy(){
System.out.println(Thread.currentThread().getName()+"ok");
}
private static Lazy lazy;
public static Lazy getInstance(){
if(lazy==null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();
/**
* 不是原子性操作
* 1. 分配内存空间
* 2. 执行构造方法 初始化对象
* 3. 把对象指向 内存空间
*
* 会进行指令重排
* A 线程的执行顺序可能是1 3 2
* 当A 执行完3 还没执行2的时候 线程B进来执行 由于执行完3 会认为
lazy
* 不为null(因为已经指向了内存空间) 所以B会返回未完成构造的lazy
* 需要在lazy上加volatile修饰
*/
}
}
}
return lazy; //lazy可能还未完成构造,它的空间是虚无的。
}
}
此方法会造成指令重排序。
volatile关键字作用:
-
保证可见性(变量都在主存进行操作)
-
禁止指令重排序,建立内存屏障;
-
不能保证原子性。
private static volatile Lazy lazy;
最终版 高性能,安全,懒汉式
public class Lazy {
private Lazy(){}
private static volatile Lazy lazy;
public static Lazy getInstance(){
if(lazy==null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();
}
}
}
return lazy;
}
}