Java学习笔记Java设计模式

单例模式

2017-06-04  本文已影响137人  起个名忒难

概述

单例模式是比较简单的设计模式,使用也是非常的广泛。看一下关于单例模式的定义:

确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

它的定义包含了三个要点:

来看一下单例模式的通用代码:

饿汉式单例

class Singleton {
    private static final Singleton singletonTest = new Singleton();  //创建实例
    private Singleton() {}   //构造方法私有

    public static Singleton getInstance(){
        return singletonTest ;  //返回实例
    }
}

public class SingletonTest{
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance() ;
        Singleton s2 = Singleton.getInstance();

        System.out.println(s1 == s2);  //true
    }
}

上面这种写法是最简单的写法,但是有一个问题,就是在类加载的时候就创建了实例,而不管是否需要创建这个实例,没有做到延迟加载 ,增加了负载。

懒汉式单例

为了解决上面的问题,对上面的通用代码进行如下改进,当系统需要该实例的时候,才产生实例对象,并返回,代码如下:

class LazySingleton{

    private static  LazySingleton  singleton = null ;

    private LazySingleton(){}

    public static LazySingleton getInstance(){
        if(singleton == null ){
            singleton = new LazySingleton() ;
        }
        return  singleton ;
    }
}

通过上面的代码发现,在类加载的时候并没有进行类的初始化操作,只有在第一次调用getInstance()方法时,才进行了实例化操作,实现了延迟加载的功能。那么问题又来了,啥问题问题,上面的类在单线程环境是没有问题的,但是在多线程环境中是不安全的无法保证实例的唯一性,那么在改进一下,看下面代码:

class LazySingleton{

    private static  LazySingleton  singleton = null ;

    private LazySingleton(){}

    public static synchronized LazySingleton getInstance(){
        if(singleton == null ){
            singleton = new LazySingleton() ;
        }
        return  singleton ;
    }
}

在getInstance的方法上添加了synchronized关键字,来保证线程之间的同步,确保实例的唯一性。看起来已经解决了上面的问题,但是新的问题其实又出现了,什么问题呢? 就是每次访问getInstance()方法时都进进行线程锁定的判断,在多线程高并发的环境下,使得系统的性能大大的降低了,那么有什么办法来解决这个问题吗? 当然有,看下面代码:

class LazySingleton{

    private static  LazySingleton  singleton = null ;

    private LazySingleton(){}

    public static LazySingleton getInstance(){
        if(singleton == null ){
            synchronized (LazySingleton.class){
                singleton = new LazySingleton() ;
            }

        }
        return  singleton ;
    }
}

最初为了保证线程之间的同步问题,给getInstance()的整个方法加了锁,其实要保证同步的罪魁祸首其实是 singleton=new LazySingleton()这句,只要保证了它的同步,产生的实例就是唯一的,所以使用synchronized块,来保证线程之间的同步。 这样在完成了singleton初始化之后,便不会在进入synchronized中,系统的性能便不会受影响了。 程序看起来已经很完美了,事实确实是这样吗? 当然不是,再次被打脸了,分析一下,假设现在有两个线程A ,B 都执行到了 if() 出,线程A先执行了,获得了锁,此时完成了singleton的初始化操作,释放锁 ,B开始执行,因为B并不知道此时singleton已经完成了实例的创建,会再次创建一个实例。 好的,我们在改进一下 :

class LazySingleton{

    private volatile static  LazySingleton  singleton = null ;

    private LazySingleton(){}

    public static LazySingleton getInstance(){
        if(singleton == null ){
            synchronized (LazySingleton.class){
                if(singleton == null){
                    singleton = new LazySingleton() ;
                }
            }

        }
        return  singleton ;
    }
}

在synchronized块中,又进行了一次条件判断,这种方式称为双重检查锁定。 另外在变量的声明中添加了volatile关键字。关于volatile简单说两句,由于singleton使用了volatile修饰,一旦一个线程对改变做了修改,会马上由工作内存写回到主内存中,被其他线程所读取,工作内存可以理解为被线程独享,主内存可以理解为线程共享,被volatile修饰的成员变量可以确保多个线程都能够正确的处理,但是该代码只能在jdk1.5及以上的版本中才能正确执行。现在9都快要发布了, 相信现在还运行在1.5以下的程序应该不是很多了,应该影响不大, 这里只是提一下。 volatile关键字会禁止指令重排序优化,屏蔽到java虚拟机所做的一下代码优化,可能会导致运行效率降低, 这看起来也不是一个非常完美的实现方式。

饿汉式单例和懒汉式单例的比较

既然饿汉式和懒汉式都存在自己的问题,有没有一种方式能解决它们的问题呢,当然有,看下面代码:

通过使用静态内部类实现单例

public class StaticInnerClass {
    private StaticInnerClass(){}
    private static class InnerClass{
        private static final StaticInnerClass singleton = new StaticInnerClass() ;
    }

    public static StaticInnerClass getInstance(){
        return InnerClass.singleton ;
    }
}

调用getInstance()方法时,才进行实例的初始化操作,由于是static变量只会进行一次初始化操作,有JVM来保证线程的安全性。 即实现了延迟加载,有可以保证线程的安全。

枚举实现单例

//单例类
class Resource{
    public Resource(){
        System.out.println("resouce 构造方法");
    }
}

//枚举类生成Resource的单个实例
public enum EnumSingleton{
    INSTANCE ;

    private Resource resource ;
    private EnumSingleton(){
        System.out.println("EnumSingleton 构造方法执行");
        resource = new Resource() ;
    }

    public Resource getInstance(){
        return  resource ;
    }
}

//测试类及运行结果
public class Test {
    public static void main(String[] args) {
        Resource s1 = EnumSingleton.INSTANCE.getInstance() ;
        Resource s4 = EnumSingleton.INSTANCE.getInstance() ;
        Resource s3 = EnumSingleton.INSTANCE.getInstance() ;
        Resource s2 = EnumSingleton.INSTANCE.getInstance() ;

        if(s1==s2 && s2 ==s3 && s3==s4 && s1 ==s4){
            System.out.println("同一个引用");
        }
    }
}

//运行结果
EnumSingleton 构造方法执行
resouce 构造方法
同一个引用

在枚举类中构造方法私有化,外部无法访问,在访问枚举实例时会执行构造方法,实例化Resouce对象,枚举实例都是static final类型的,只能被实例化一次。这也是effective java中推荐的方式。

单例模式的优点:

缺点:

使用场景:

要求一个类有且仅有一个对象,只允许有一个公共的访问节点。 可以使用单例模式。


少年听雨歌楼上,红烛昏罗帐。  
壮年听雨客舟中,江阔云低,断雁叫西风。
感谢支持!
                                        ---起个名忒难

上一篇下一篇

猜你喜欢

热点阅读