懒汉式单例

本文探讨了懒汉式单例模式的问题,如指令重排导致的并发问题,并介绍了双重检测和volatile关键字的解决方案,以及ThreadLocal和内部类的使用技巧。通过实例对比,展示了如何确保线程安全和正确初始化Singleton对象。
摘要由CSDN通过智能技术生成

懒汉式单例

双重检测

//懒汉式单例
public class Singleton {
    private static Singleton singleton=null;

    //避免外区去new
    private Singleton() {
    }

    public static Singleton getSingleton() {
        if(singleton==null)
            synchronized (Singleton.class){
                if(singleton==null){
                    singleton=new Singleton();
                }
        }
        return singleton;
    }
}

存在什么问题呢?

指令重排,在singleton=new Singleton();执行这行代码的时候可能会由以下几种操作

  1. 申请内存,调用构造方法初始化
  2. 分配指针指向这块内存

在指令重排下可能1 2 的顺序是不一定谁先谁后的,好比2先了,然而1还没执行,此时另一个线程来了,直接判断singleton不是空,就会直接返回singleton,但是此时singleton是没有完全初始化好的,这样就会产生问题

为了避免这种问题,是如何解决的?

双重检测使用static修复

在变量前加个voliate,在JDK5之前,volatile并没有明确定义规定和用途,5后被volatile修饰的写变量不能和之前的读写代码调整,读变量不能和之后的读写代码调整! 因此,只要我们简单的把singlton加上volatile

//懒汉式单例
public class Singleton {
    private volatile static Singleton singleton=null;

    //避免外区去new
    private Singleton() {
    }

    public static Singleton getSingleton() {
        if(singleton==null)
            synchronized (Singleton.class){
                if(singleton==null){
                    singleton=new Singleton();
                }
        }
        return singleton;
    }
}

参考链接

java 内存屏障

内存屏障也称为内存栅栏或栅栏指令,是一种屏障指令,它使CPU或编译器对屏障指令之前和之后发出的内存操作执行一个排序约束。 这通常意味着在屏障之前发布的操作被保证在屏障之后发布的操作之前执行。

java 的内存屏障通常所谓的四种即LoadLoad,StoreStore,LoadStore,StoreLoad实际上也是上述两种的组合,完成一系列的屏障和数据同步功能。

Load 指令(也就是从内存读取),Store指令 (也就是写入内存)。)

  • LoadLoad 屏障:对于这样的语句 Load1; LoadLoad; Load2 ,在 Load2 及后续读取操作要读取的数据被访问前,保证Load1 要读取的数据被读取完毕。
  • StoreStore 屏障:对于这样的语句 Store1; StoreStore; Store2 ,在 Store2 及后续写入操作执行前,保证 Store1 的写入操作对其它处理器可见。
  • LoadStore 屏障:对于这样的语句 Load1; LoadStore; Store2 ,在 Store2 及后续写入操作被刷出前,保证 Load1 要读取的数据被读取完毕。
  • StoreLoad 屏障:对于这样的语句 Store1; StoreLoad; Load2 ,在 Load2 及后续所有读取操作执行前,保证 Store1 的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能
volatile 做了什么

在一个变量被 volatile 修饰后,JVM 会为我们做两件事:

  1. 在每个 volatile 写操作前插入 StoreStore 屏障,在写操作后插入 StoreLoad 屏障。(StoreStore-写-StoreLoad
  2. 在每个 volatile 读操作前插入 LoadLoad 屏障,在读操作后插入LoadStore屏障。(LoadLoad-读-LoadStore

意思是:在你对这个变量进行读或者写的时候,在这之前的读写必须完毕,

那么这样的话就避免了这样的问题,保证了不管1 2 指令如何重排,在这之后的线程读这个singleton时候前面的读写会完毕,如果前面只指令了第2步,那么新来的线程会去等待第一步完成,因为第一步涉及了对这个变量singleton的读

JDK1.2 还是1.5前好像volatile没有实现这样的作用,会导致双重检测时候出现问题那怎么办呢?使用ThreadLocal修复双重检

使用ThreadLocal修复双重检测

这里只是用ThreadLocal它当做标识而已,也可以自定义一个类来用

//懒汉式单例
public class Singleton {
    private static Singleton singleton=null; //volatile可以不用写了
    private static final ThreadLocal hasInstance=new ThreadLocal();

    //避免外区去new
    private Singleton() {
    }

    public static Singleton getSingleton() {
        //不等于空说明已经创建好了 而且不管指令会重排还是不排,这个代码不等于空的时候指令重排的代码所有都执行完了,不会执行一办
        if (hasInstance.get()==null){
            
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();//指令会重排的地方
                }
                hasInstance.set(hasInstance);
                return singleton;
                
            }
        }
        return singleton;
    }
}

使用内部类实现懒加载

//懒汉式单例
public class Singleton {

    //避免外区去new
    private Singleton() {
    }
    public static class Holder{  //权限修饰符也可以是private  对外公开也没事,因为static修饰singleton这个变量只会加载一次
          static Singleton singleton=new Singleton();
    }

    public static Singleton getSingleton() {
        return Holder.singleton;
        // return new Holder().singleton; 这句也可以,因为static修是了
    }
}

测试类

//测试类
public class Test {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getSingleton();
        Singleton singleton1 = Singleton.getSingleton();
        Singleton singleton2 = new Singleton.Holder().singleton;//Singleton.Holder.singleton;也可以写成这样,都行
        System.out.println(singleton==singleton1);
        System.out.println(singleton==singleton2);
    }
}
true
true

static去掉后

//懒汉式单例
public class Singleton {

    //避免外区去new
    private Singleton() {
    }
    public static class Holder{  //权限修饰符也可以是private  对外公开也没事,因为static修饰singleton这个变量只会加载一次
          Singleton singleton=new Singleton(); //类里面的实例变量,每个人new 这个类的时候都是不一样的,如果用static修饰的话就都一样的

        public Singleton getSingleton() {
            return singleton;
        }
    }

    public static Singleton getSingleton() {
        return new Holder().singleton;
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getSingleton();
        Singleton singleton1 = Singleton.getSingleton();
        //Singleton singleton2 = new Singleton.Holder().singleton;
        Singleton singleton2 = new Singleton.Holder().singleton;
        System.out.println(singleton==singleton1);
        System.out.println(singleton==singleton2);
    }
}
false
false

参考链接:http://www.iteye.com/topic/65244

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值