单例设计模式

单例设计模式的意思就是,确保一个类只能有一个对象。任何别的方法什么的对该类的操作全是对这一个对象的操作。如同我们电脑上的回收站。无论那个盘都有一个回收站的空间。但都是同一个。可以在回收站里全删除了。Jvm里面的GC也是一个意思。

单例设计模式有两种情况。

一、懒汉式

  1. 声明一个私有的静态变量
  2. 构造器私有化
  3. 创建一个对外的公有静态方法访问该变量,如果变量不存在,创建该对象。如果存在则返回这个私有的静态变量。这样就能保证外部在使用该类的对象时只会用同一个。

基本的懒汉式

class Jvm{
    //声明一个私有的静态变量
    private static Jvm instance;
    //构造器私有化
    private Jvm(){
    }
    //创建一个对外的公有静态方法访问该变量,如果变量不存在,创建该对象
    public static Jvm getJvm(){
        
        if(null==instance){
            instance=new Jvm();
        }
        
        return instance;
    }
}

这样在执行下列语句

Jvm jvm1=Jvm.getJvm();
Jvm jvm2=Jvm.getJvm();
System.out.println(jvm1);
System.out.println(jvm2);

他们得到的对象都是一样的了。地址一样即为同一个对象。

synTest.Jvm@15db9742
synTest.Jvm@15db9742

但是这样的懒汉式却是不安全的。如果是多线程来使用这个类,就会存在延迟。可能会产生多个对象。
这里我们放大他的延迟时间更容易的得到错误的结果。
修改上面的Jvm类。给他设置一个延迟时间

class Jvm1{
    private static Jvm1 instance;

    private Jvm1() {
    }
    
    public static Jvm1 getJvm(long time) {
        if (null == instance) {
            try {
                Thread.sleep(time); //这里由外部给定延迟时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new Jvm1();
        }
        return instance;
    }
}

创建使用这个Jvm1的线程

class JvmThread extends Thread{
    private long time;
    
    public JvmThread() {
    }
    //为方便查看,这里构造器也传个name,给这个线程设置名字。
    public JvmThread(long time,String name){
        this.time=time;
        //父类继承了私有的name,直接设置即可
        super.setName(name);
    }
    
    public void run() {
        System.out.println(Thread.currentThread().getName()+"创建了:"+Jvm1.getJvm(this.time));
        //这里直接在线程里面输出地址
    }
}

执行下列语句

JvmThread jth1=new JvmThread(500,"a");
JvmThread jth2=new JvmThread(1000,"b");
jth1.start();
jth2.start();

上面两个线程都使用了Jvm1类。构造了。按照单例模式的要求,对象地址应该是一样的。但结果如下

a创建了:synTest.Jvm1@43e8772d
b创建了:synTest.Jvm1@20b708e4

地址却不一样,下面分析

单例设计模式_第1张图片
分析

所以这里就需要用到同步措施,使线程变得安全。
比较简单的方法是直接使用同步方法,或同步块

class Jvm1{
    private static Jvm1 instance;

    private Jvm1() {
    }
    //这个方法改成同步方法即可
    public static synchronized Jvm1 getJvm1(long time) {
        if (null == instance) {
            try {
                Thread.sleep(time); //这里由外部给定延迟时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new Jvm1();
        }
        return instance;
    }
}

或者同步块

public static Jvm1 getJvm1(long time) {
        synchronized(Jvm1.class){
            if (null == instance) {
                try {
                    Thread.sleep(time); //这里由外部给定延迟时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                instance = new Jvm1();
            }   
            return instance;
        }
    }

但是这样子设置会出现一个问题,每一个线程调用这个方法都得等待,即使已经有对象了,还需要等待。
稍作改进,人称double check

public static Jvm1 getJvm1(long time) {
        if(null==instance){
            synchronized(Jvm1.class){
                if (null == instance) {
                    try {
                        Thread.sleep(time); //这里由外部给定延迟时间
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    instance = new Jvm1();
                }   
            }
        }
    return instance;
    }

这样子,如果线程调用时,已经有对象的时候,就可以直接返回啦。不需要进去等待。而如果多个线程同时在没有对象时调用。也会进入那个synchronized块。不会生成多个对象。


二、饿汉式
1.构造器私有化
2.声明私有的静态属性,同时创建对象
3.对外提供访问属性的静态方法

饿汉式由于对象直接构建了,所以调用都是调用那个对象,不存在线程安全问题了。

饿汉式也有普通的和完善的。

基本饿汉式

class MyJvm{
    //声明私有的静态变量,同时构建对象
    private static MyJvm instance=new MyJvm();
    
    //构造器私有
    private MyJvm() {
    }
    
    public static MyJvm getMyJvm(){
        return instance;
    }
}

这里有一点不好的就是,在声明MyJvm时候,就构造了他的对象。如果MyJvm里面还有其他的静态方法的话,并且也只使用其他方法,那么那个对象的必要性就没有了。所以做如下改进。

class MyJvm2{
    //这里定义一个静态内部类。类是在调用的时候才会加载的。
    //当调用了下面的getMyJvm2方法时,才会用到MyJvm2Holder类,才会加载这个类。就会有那个对象了。
    private static class MyJvm2Holder{
        private static MyJvm2 instance=new MyJvm2();
    }
    
    private MyJvm2() {
    }
    
    public static MyJvm2 getMyJvm2(){
        return MyJvm2Holder.instance;
    }
}

你可能感兴趣的:(单例设计模式)