设计模式——单例模式

关于单例模式,主要是为了一个类每次在使用它的实例时,都是同一个实例对象。

单例模式一

public class MySingleton {  
      
    private static MySingleton instance = new MySingleton();  
      
    private MySingleton(){}  
      
    public static MySingleton getInstance() {  
        return instance;  
    }  
      
} 

上述的单例模式代码一定是线程安全的,并且每次调用都会返回同一个对象,线程安全的原因是,静态变量是在类的初始化过程当中赋值,这个类在调用的时候,如果没有对类进行初始化,就会进行初始化。唯一的缺点就是,如果我们程序当中一直没有调用getInstance方法,但是我们对这个类进行了初始化,会占用一定的内存空间,因此这种实现方式显然不是最优的,

单例模式二

public class MySingleton{  
       
    private static MySingleton instance = null;  
       
    private MySingleton(){}  
  
    static{  
        instance = new MySingleton();  
    }  
      
    public static MySingleton getInstance() {   
        return instance;  
    }   
}  

这个方式的实现和上面第一个的方式实现方式基本上一样,都是属于在类初始化时候进行了对象的实例化,但是有可能初始化的时候,我们并没有使用getInstance方法,因此就造成了空间上的浪费。注:静态变量和静态代码块都会在类初始化的时候尽心加载。

单例三

由于上面两个方式都存在内存浪费的一种情况,那我们就设计一种,在使用到这个对象的时候,在进行实例化的方式

public class MySingleton {  
      
    private static MySingleton instance = null;  
      
    private MySingleton(){}  
      
    public static MySingleton getInstance() {  
        if(instance == null){//懒汉式  
            instance = new MySingleton();  
        }  
        return instance;  
    }  
}  

很明显这种方式最大的问题就是线程不安全,考虑到线程安全,就有了实例四的实现方式,直接在方法上加synchronious

单例四

public class MySingleton {  
      
    private static MySingleton instance = null;  
      
    private MySingleton(){}  
      
    public synchronized static MySingleton getInstance() {  
        try {   
            if(instance == null){//懒汉式   
                instance = new MySingleton();  
            }  
        } catch (InterruptedException e) {   
            e.printStackTrace();  
        }  
        return instance;  
    }  
}  

这种方式呢,即实现了线程安全,又省了空间,但是呢,也有它的问题synchronous这个锁加的范围太大了,那么接下来就有了下面的,同步代码块,只对需要加锁的地方进行加锁,用不到的都不加锁。

单例五

public class MySingleton {  
      
    private static MySingleton instance = null;  
      
    private MySingleton(){}  
       
    public static MySingleton getInstance() {  
        try {    
            if(instance == null){//懒汉式   
                  
                //创建实例之前可能会有一些准备性的耗时工作   
                Thread.sleep(300);  
                synchronized (MySingleton.class) {  
                    instance = new MySingleton();  
                }  
            }   
        } catch (InterruptedException e) {   
            e.printStackTrace();  
        }  
        return instance;  
    }  
}  

这个看起来已经很完美了,写个代码测试下

class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(Singleton.getInstance().hashCode());
    }
}

---------------------------------------------
 public static void main(String[] args) {
        MyThread[] thread = new MyThread[10];
        for(int i = 0; i < thread.length; i++ ){
            thread[i] = new MyThread();
        }
        for(int i = 0; i < thread.length; i++ ){
            thread[i].start();
        }
}

上述打印出来的hash值并不相同,原因在于虽然我们对创建部分进行了加锁,但是需要二次检查

单例六

public class MySingleton {  
      
    //使用volatile关键字保其可见性  
    volatile private static MySingleton instance = null;  
      
    private MySingleton(){}  
       
    public static MySingleton getInstance() {  
        try {    
            if(instance == null){//懒汉式   
                 
                //创建实例之前可能会有一些准备性的耗时工作   
                Thread.sleep(300);  
                synchronized (MySingleton.class) {  
                    if(instance == null){//二次检查  
                        instance = new MySingleton();  
                    }  
                }  
            }   
        } catch (InterruptedException e) {   
            e.printStackTrace();  
        }  
        return instance;  
    }  
}  

这种情况下才能保证每次打印出来的对象都是同一个对象。但是以上我们介绍的所有的实现方式,在序列化和反序列化的时候,是否还能保证对象相同,我们看下测试代码

 public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();

        File file = new File("C:\\Users\\Administrator\\Desktop\\MySingleton.txt");

        try {
            FileOutputStream fos = new FileOutputStream(file);
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(singleton);
            fos.close();
            oos.close();
            System.out.println(singleton.hashCode());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            FileInputStream fis = new FileInputStream(file);
            ObjectInputStream ois = new ObjectInputStream(fis);
            Singleton rSingleton = (Singleton) ois.readObject();
            fis.close();
            ois.close();
            System.out.println(rSingleton.hashCode());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }

这时候打印出来结果hash值并不相同,所以之前的单例实现还需继续修改,在MySingleton方法当中实现readResolve方法即可

//该方法在反序列化时会被调用,该方法不是接口定义的方法,有点儿约定俗成的感觉  
    protected Object readResolve() throws ObjectStreamException {  
        System.out.println("调用了readResolve方法!");  
        return MySingletonHandler.instance;   
    }  

单例七

使用静态内部类

public class MySingleton implements Serializable {  
       
    private static final long serialVersionUID = 1L;  
  
    //内部类  
    private static class MySingletonHandler{  
        private static MySingleton instance = new MySingleton();  
    }   
      
    private MySingleton(){}  
       
    public static MySingleton getInstance() {   
        return MySingletonHandler.instance;  
    }  
}  

静态内部类不需要加锁,同时也能保证线程安全而且也不会浪费内存空间,原因在于,外部类在初始化的时候,如果没有使用到内部类,是不会对内部类进行初始化,只有当我们显示调用静态内部的静态变量或者静态方法时,才会对静态内部类进行初始化。

这里没有给出枚举的实现方式。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值