设计模式——单例模式

什么是单例模式

在应用中一个类仅有一个实例,这个实例由类本身提供,客户端无法自主创建实例

 

单例模式的角色

Singleton:单例类,仅有一个实例,由单例类自己返回自己的实例

 

首先提供一种简单的写法——饿汉模式:

Printer:

public class Printer {
    
    private static Printer printer=new Printer();
    
    private Printer(){}
    
    public static Printer getPrinter(){
        return printer;
    }
    
    public void getSomethingToPrint(String thing){
        System.out.println("打印"+thing);
    }
    
}

这种写法是线程安全的,static保证了对象的唯一性,因为static修饰的对象的实例化是在JVM加载类过程中完成的,一般情况下,JVM只会加载某个类一次,关于恶汉模式的巧妙性,可以参考我的一篇博客:java中的单体类

 

恶汉模式有一点问题,假设某个类的实例化需要耗费许多系统资源,并且占据的内存也比较大,但是这个对象并不急着使用,我们想先加载这个类,但并不想立刻使用这个类的实例(这种情况是存在的),此时恶汉模式就会出现问题,static保证加载这个类时JVM就会实例化这个类,如何解决?答案是按需加载(这种思想在计算机中非常常见)

 

按需加载使用的实现方式——懒汉模式:

public  class ThreadSafeSingleton  
{  
    private static ThreadSafeSingleton singObj = null;  
  
    private ThreadSafeSingleton(){  
    }  
  
    public static Synchronized ThreadSafeSingleton getSingleInstance(){  
        if(null == singObj ) singObj = new ThreadSafeSingleton();
            return singObj;
    }  
}

这种实现在多线程下不会出现问题,但是效率非常的低,因为使用了锁,所以当多个线程同时请求这个实例时,就会出现排队使用的情况,有没有效率高,并且不会出现线程问题的写法呢?答案是有的,而且设计的非常精妙:

public class Singleton    
{    
    private Singleton(){}
    
    private static class SingletonHolder    
    {    
        public static Singleton instance = new Singleton();    
    }    
   
    public static Singleton getInstance()    
    {    
        return SingletonHolder.instance;    
    }

    public void print(){    
        System.out.println("输出");
    }
}

 

private修饰内部类,可以隐藏内部类,static修饰内部类,一方面是因为getInstance()为静态方法,仅能使用static修饰的类、属性、方法,另一方面是instance必须是static修饰,如果不是static修饰,则必须先实例化SingletonHolder(此时instance才会被初始化),为了保证Singleton的唯一性(每次返回都是同一个实例),SingletonHolder必须具有唯一性,这又是一个单例问题(将问题复杂化了),所以static修饰instance是必须的(可以简化问题),当我们第一次调用getInstance方法时,JVM才会去加载SingletonHolder,按需加载使用的目的达成,并且整个过程只会实例化Singleton类,内部类不会实例化。

 

还有一种写法叫做双重检查模式,这种写法在多线程下是会有问题的:
 

public class DoubleCheckedSingleton  
{  
    private static DoubleCheckedSingletonsingObj = null;  
  
    private DoubleCheckedSingleton(){  
    }  
  
    public static DoubleCheckedSingleton getSingleInstance(){  
        if(null == singObj ) {
              Synchronized(DoubleCheckedSingleton.class){
                     if(null == singObj)
                           singObj = new DoubleCheckedSingleton();
              }
         }
       return singObj;
    }  
}

JVM实例化一个类时,会先在堆中申请一块内存,接着执行构造函数,本例中,当有多个线程执行时,线程A开始实例化singObj,此时singObj已经在堆中有地址了,但singObj的构造函数执行比较慢,此时线程B执行getSingleInstance()函数,由于singObj有了地址,所以线程B直接得到singObj对象,但singObj对象的构造函数并未执行完毕,此时部分值可能就为空

 

使用单例模式时,由于单例是一块共有资源,多线程下需要注意是否存在改值问题,使用单例模式更多是因为使用需求要求某个类仅有一个实例,所以单例模式的优点我看不太出来,但单例模式的几种写法的技巧性比较高,也非常精妙

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值