Java中几种常见的设计模式--单例设计模式

一、什么是单例设计模式

单例设计模式的定义是只有一个类,并且提供一个全局访问点。

二、适用于那些场景

一个对象即可完成所有工作,无需大量创建对象消耗资源。比如一个长连接,建立起来就不断的发送数据,如果每一个请求都创建一个链接,资源很快就被消耗殆尽。

三、有什么特点

  • 只有一个实例
  • 自我实例化
  • 提供一个全局访问点

四、单例模式的优缺点

 优点:由于单例模式只生成了一个实例,所以能够节约系统资源,减少性能开销,提高系统效率,同时也能够严格控制客户对它的访问。

 缺点:也正是因为系统中只有一个实例,这样就导致了单例类的职责过重,违背了“单一职责原则”,同时也没有抽象类,这样扩展起来有一定的困难

五、如何创建

方法一 饿汉模式

使用静态常量,在单例类加载的时候就创建了,常见代码如下:

class Singleton1 {
    //类内部创建实例
    private static Singleton1 instance=new Singleton1();
    
    //构造方法私有化,防止通过new方式 创建私立
    private Singleton1(){}
    
    //对外提供全局同用的唯一调用方法
    public static Singleton1  getInstance(){
        return instance;
    }
    
}

这种方式优点缺点都很明显,

优点:类加载的时候实例化,防止对线程同时访问问题。

缺点:类加载时候就实例化,如果没有使用就会造成内存空间的浪费。

方法二懒汉模式

为了解决饿汉模式对于不使用造成内存空间浪费的缺点,又有人提出了懒汉模式,懒汉模式就是在使用时才创建实例,如果不使用就不会创建。

public class Singleton2 {
    
    //先声明句柄,但不立即创建实例
    private static Singleton2 instance;
    
    //构造方法私有化,防止外部通过使用new方式创建实例
    private Singleton2(){}
    
    //想外部提供全局共享的唯一实例化接口方法
    public static Singleton2 getInstance(){
        if(instance==null){ 
            instance=new Singleton2();
        }
        return instance;
    }

}

但这种懒汉模式也存在一个很大的问题,它有多线程问题,在多个线程同时访问的时候并不能保证单例,我们使用多线程去获取单例,发现获取的实例并不唯一。

public class mytest {
    public static void main(String[] args){
        
        long l1=System.currentTimeMillis();
        ExecutorService es=Executors.newFixedThreadPool(10);
        for(int i=0;i<10;i++){
                es.execute(new Runnable(){
                    @Override
                    public void run(){
                        Singleton2 s2=Singleton2.getInstance();
                        System.out.println(s2);
                    }
                });
            }    
        es.shutdown();
        while(!es.isTerminated()){}
        long l2=System.currentTimeMillis();
        System.out.println(l2-l1);
    }
}

zc.com.examp.test1.core.Singleton2@1ffffbce
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@1ffffbce
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
5

这是因为在执行instance==null语句时,此时还没有创建实例,此时满足条件的线程都进入,执行instance=new Singleton2()语句。对于此问题,我们可以使用synchronized关键字,让getInstance()方法只能同时被一个线程访问。

public class Singleton2 {
    
    //先声明句柄,但不立即创建实例
    private static Singleton2 instance;
    
    //构造方法私有化,防止外部通过使用new方式创建实例
    private Singleton2(){}
    
    //想外部提供全局共享的唯一实例化接口方法
    public static synchronized Singleton2 getInstance(){
        if(instance==null){ 
            instance=new Singleton2();
        }
        return instance;
    }    

}

测试结果如下:

zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
9

可以看到通过使用Synchronized关键字确实可以解决多线程同时访问的问题,但同时也降低了执行效率,每次调用getInstance()方法都要涉及锁的操作。其实我们可以对上面的代码进一步优化。

public class Singleton2 {
    
    //先声明句柄,但不立即创建实例
    private static Singleton2 instance;
    
    //构造方法私有化,防止外部通过使用new方式创建实例
    private Singleton2(){}
    
    //想外部提供全局共享的唯一实例化接口方法
    public static Singleton2 getInstance(){
        if(instance==null){ 
             //只有在第一次使用的时候构造实例对象,使用synchronized代码块和双重判断避免多线程问题,并且提供效率
            synchronized(Singleton2.class){
                if(instance==null){
                    instance=new Singleton2();
                }
            }
            
        }
        return instance;
    }    

}

测试结果:

zc.com.examp.test1.core.Singleton2@4c75281a
zc.com.examp.test1.core.Singleton2@4c75281a
zc.com.examp.test1.core.Singleton2@4c75281a
zc.com.examp.test1.core.Singleton2@4c75281a
zc.com.examp.test1.core.Singleton2@4c75281a
zc.com.examp.test1.core.Singleton2@4c75281a
zc.com.examp.test1.core.Singleton2@4c75281a
zc.com.examp.test1.core.Singleton2@4c75281a
zc.com.examp.test1.core.Singleton2@4c75281a
zc.com.examp.test1.core.Singleton2@4c75281a
5

这种方式不仅解决了线程不安全问题,又提升了执行效率,只有前面的少数线程可能会获取锁,只要实例被创建,后面的线程一般只需第一个if判断就返回了对象,所以这种方式推荐使用。

方法三内部静态类。

我们知道饿汉模式缺点就是类在加载时候就创建了实例,容易造成内存资源的浪费,如果在单例类中再创建一个静态内部类,外部类装载的时候静态内部类不会装载,只有使用的时候才会装载,因此达成了懒汉式的效果,实现代码如下:

class Singleton3 {
    
    //构造方法私有化,防止通过new方式 创建私立
    private Singleton3(){}
    
     //静态内部类,在外部类加载的时候不会加载静态内部类
    private static class SingletonInstance{
        static Singleton3 instance=new Singleton3();
    }
    
    //对外提供全局同用的唯一调用方法
    public static Singleton3  getInstance(){
        //只有在使用到静态内部类的时候才会加载,并且通过类加载机制保证在初始化的时候只有一个实例产生
        return SingletonInstance.instance;
    }
    
}

 

 

-----------------------------------------------------------------------------------------------------------------

参考博文:https://www.cnblogs.com/garryfu/p/7976546.html

参考博文:https://www.cnblogs.com/ye-feng-yu/p/11183075.html

 

posted @ 2020-07-07 22:34  Jerryoned  阅读(354)  评论(0编辑  收藏  举报