单例模式学习心得

单例模式的设计有懒汉式和饿汉式。

懒汉式指该实例的创建是在调用该类对外开放的get接口时才生成。

饿汉式指该实例的创建在为调用时就已经存在。

下面介绍集中单例模式的写法

1.

public class SinglePattern {
    private static SinglePattern singlePattern = null;
    
    private SinglePattern(){
        System.out.println("Hi");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    public static SinglePattern getSinglePattern(){
        
        if( singlePattern == null ){
            init();
        }
        System.out.println("获取实例成功:" + singlePattern.toString());
        return singlePattern;
    }
    
    private static void init(){
        singlePattern = new SinglePattern();
    }

}

这是最简单的一种懒汉式写法,该类的实例会在第一次调用的时候生成。但是,这种方法是一种线程不安全的写法,在多线程中,一但两个线程同时发起,同时进入了实例化的过程时,那么将会在不同的线程有两个实例。

测试代码如下, 注意下面的几种方法如果没有特别声明测试代码都是使用这一段

public class Test extends Thread{

    public void run(){
        SinglePattern.getSinglePattern();
    }
    
    public static void main(String[] args){
        Thread thread1 = new Test();
        Thread thread2 = new Test();
        thread1.start();
        thread2.start();
    }
}

结果

Hi
Hi
获取实例成功:SinglePattern@527c6768
获取实例成功:SinglePattern@65690726

可以发现SinglePattern的实例初始化了两次。

2.

public class SinglePattern {
    private static SinglePattern singlePattern = null;
    
    private SinglePattern(){
        System.out.println("Hi");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    public static synchronized SinglePattern getSinglePattern(){
        
        if( singlePattern == null ){
            init();
        }
        System.out.println("获取实例成功:" + singlePattern.toString());
        return singlePattern;
    }
    
    private static void init(){
        singlePattern = new SinglePattern();
    }

}

这是第一种方法的简单变形,将整个函数加锁保证整个函数只能被调用一次,但是会造成效率的降低,因为我们第二次以后的调用只需要获取前面实例化出来的对象,但由于整个函数都被加锁刘,所以该函数的调用还是需要等待,造成不必要的效率浪费。

测试代码还是上面那个Test类。

结果如下

Hi
获取实例成功:SinglePattern@55f33675
获取实例成功:SinglePattern@55f33675

此处注意一点,如果是给init() 函数加上锁而不是getSinglePattern()函数是没有作用的,因为它们会在init函数前面等待,两个线程会依次执行init函数,实例化出两个对象。当然,给init()函数加上锁的思路是正确的,因为我们只需要保证在创建的时候保持同步即可,而不是整个getSinglePattern()方法都加上锁。看下文

3.

public class SinglePattern {
    private static SinglePattern singlePattern = null;
    
    private SinglePattern(){
        System.out.println("Hi");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    public static SinglePattern getSinglePattern(){
        
        if( singlePattern == null ){
            init();
            
        }
        System.out.println("获取实例成功:" + singlePattern.toString());
        return singlePattern;
    }
    
    private static synchronized  void init(){
        if( singlePattern == null )
            singlePattern = new SinglePattern();
    }

}

这里使用了双重校验锁的方法,即使有多个线程进入了init()方法,但是除了第一个线程会实例化对象外,其它进入的线程都会发现singlePattern对象已经被实例了。

测试结果

Hi
获取实例成功:SinglePattern@55f33675
获取实例成功:SinglePattern@55f33675

4.

public class SinglePattern {
    private static SinglePattern singlePattern = init();
    
    private SinglePattern(){
        System.out.println("Hi");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    public static SinglePattern getSinglePattern(){
        
        System.out.println("获取实例成功:" + singlePattern.toString());
        return singlePattern;
    }
    
    private static  SinglePattern init(){
        return new SinglePattern();
    }

}

这是一种饿汉式的单例模式写法,这种方式基于类加载器的机制,利用了类装载器自身的锁机制保证了singlePattern对象拥有单一实例。关于更多类装载器的知识可以看我的其他博文这里,和这里还有这里,当然,装载SinglePattern这个类使用的是AppClassLoader系统类装载器,但是这种方法有一定的缺陷,即该类的装载可能会发生在很多的情况下,比如将该类放在了ExtClassLoader扩展类加载器的路径下,那么该类会自动装载。即,即使你在整个程序中没有使用到该对象,该对象也依然会实例化,造成了内存空间的浪费。

测试代码

public class Test extends Thread{

    public void run(){
        SinglePattern.getSinglePattern();
    }
    
    public static void main(String[] args){
        try {
            Class c =Class.forName("SinglePattern");
            System.out.println(c.getClassLoader());
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        Thread thread1 = new Test();
        Thread thread2 = new Test();
        thread1.start();
        thread2.start();
    }
}

测试结果

可以发现,在系统装载器装载完后,singlePattern对象的实例化已经完成。

Hi
sun.misc.Launcher$AppClassLoader@3326b249
获取实例成功:SinglePattern@55f33675
获取实例成功:SinglePattern@55f33675

5.

public class SinglePattern {
    private static SinglePattern singlePattern;
    
    private SinglePattern(){
        System.out.println("Hi");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    public static SinglePattern getSinglePattern(){
        if( singlePattern == null )
            singlePattern = SinglePatternHolder.singlePatternHolder;
        System.out.println("获取实例成功:" + singlePattern.toString());
        return singlePattern;
    }
    
    private static  class  SinglePatternHolder{
        static{
            System.out.println("我被加载了");
        }
        private static SinglePattern singlePatternHolder = init();
        
        private static  SinglePattern init(){
            return new SinglePattern();
        }
    }

}

这种写法利用了内部类来获取SinglePattern的实例,类装载器即使一开始就装载了SinglePattern类,但是在没有调用到getSinglePattern()方法之前是不会显示的装载内部类SinglePatternHolder,也就不会实例化对象singlePatternHolder

测试代码

public class Test extends Thread{

    public void run(){
        SinglePattern.getSinglePattern();
    }
    
    public static void main(String[] args){
        try {
            Class c =Class.forName("SinglePattern");
            System.out.println(c.getClassLoader());
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        Thread thread1 = new Test();
        Thread thread2 = new Test();
        thread1.start();
        thread2.start();
    }
}

输出结果

sun.misc.Launcher$AppClassLoader@3326b249
我被加载了
Hi
获取实例成功:SinglePattern@65690726
获取实例成功:SinglePattern@65690726

可以看出只有在调用了getSinglePattern()方法之后,jvm才开始显示装载内部类SinglePatternHolder,并初始化静态域,这样即可以保证SinglePattern只存在一个变量,又可以保证在需要的时候才实例化对象singlePattern。

转载于:https://my.oschina.net/kakakaka/blog/351581

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值