单例模式

Java中单例模式是一个很重要的设计模式,主要作用是保证在Java程序中,某个类只有一个实例存在。单例模式避免了对象的重复创建,减少了内存的开销。

单例模式有以下特点:

1、单例类只能有一个实例。

2、单例类必须自己创建自己的唯一实例。

3、单例类必须给所有其他对象提供这一实例。

单例模式有很多种写法:

1.懒汉模式

public class SingleTon {

    private static SingleTon singleTon;

    private SingleTon() {
        // TODO Auto-generated constructor stub
    }

    public static SingleTon getInstance() {
        if (singleTon == null) {
            singleTon = new SingleTon();
        }
        return singleTon;
    }

}

懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,当线程A执行到if判断,singleTon此时为空,线程A此时让出cpu,线程B开始执行,线程B执行的时候,singleTon此时为空,线程Bnew出来一个SingleTon,此时线程A接着执行,线程A也会new出来一个SingleTon,这样就出现了多个实例。

改进的话可以考虑:

(1).在getInstance方法加锁:

public class SingleTon {

    private static SingleTon singleTon;

    private SingleTon() {
        // TODO Auto-generated constructor stub
    }

    public static synchronized SingleTon getInstance() {
        if (singleTon == null) {
            singleTon = new SingleTon();
        }
        return singleTon;
    }

}

加锁的懒汉模式看起来即解决了线程并发问题,又实现了延迟加载,然而它存在着性能问题,依然不够完美。synchronized修饰的同步方法比一般方法要慢很多,如果多次调用getInstance(),累积的性能损耗就比较大了。

(2).双重检索模式

public class SingleTon {

    private static SingleTon singleTon;

    private SingleTon() {
        // TODO Auto-generated constructor stub
    }

    public static SingleTon getInstance() {
        if (singleTon == null) {
            synchronized (SingleTon.class) {
                if (singleTon == null)
                    singleTon = new SingleTon();
            }
        }
        return singleTon;
    }

}

双重索引的模式改变了加锁的位置,只有在singleTon为空的时候,才会进行线程同步,减少了线程同步的开销,但是这样还是会存在一个问题:Java中的指令重排优化。所谓指令重排优化是指在不改变原语义的情况下,通过调整指令的执行顺序让程序运行的更快。主要问题是出在singleTon = new SingleTon();这句代码上面,这句代码大概做了3件事:

1.给 instance 分配内存调用 Singleton

2.的构造函数来初始化成员变量

3.将instance对象指向分配的内存空间

java指令集乱序会导致执行的过程可能是123或者是132,如果是132的执行顺序,则在 3 执行完毕、2 未执行之前,被另外一个抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。

解决方案可以是将 instance 变量声明成 volatile 就可以了。

private volatile static SingleTon singleTon;

volatile的一个语义是禁止指令重排序优化,也就保证了instance变量被赋值的时候对象已经是初始化过的,从而避免了上面说到的问题。

(3).静态内部类

public class SingleTon {

    private SingleTon() {
        // TODO Auto-generated constructor stub
    }

    private static class SingleTonHolder {
        private static SingleTon instance = new SingleTon();
    }

    public static SingleTon getInstance() {

        return SingleTonHolder.instance;
    }

}

2.饿汉模式

public class SingleTon {

    private static SingleTon singleTon = new SingleTon();

    private SingleTon() {
        // TODO Auto-generated constructor stub
    }

    public static SingleTon getInstance() {

        return singleTon;
    }

}

饿汉模式是最简单的一种实现方式,饿汉模式在类加载的时候就对实例进行创建,实例在整个程序周期都存在。它的好处是只在类加载的时候创建一次实例,不会存在多个线程创建多个实例的情况,避免了多线程同步的问题。它的缺点也很明显,即使这个单例没有用到也会被创建,而且在类加载之后就被创建,内存就被浪费了。

3.枚举模式

public enum SingletonMenu {

    singleTon;

}

上面提到的实现单例的方式(枚举法除外)都有共同的缺点:
1)每次反序列化一个序列化的对象时都会创建一个新的实例。

public class Client {

    public static void main(String[] args) {
        // TODO Auto-generated method stub


        SingleTon singleTon = SingleTon.getInstance();


        File file = new File("/Users/qianxin/Desktop/tmp.txt");
        try {
            file.createNewFile();

            // 序列化过程
            FileOutputStream fos = new FileOutputStream(file);
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(singleTon);
            oos.flush();
            oos.close();
            fos.close();

            // 反序列化过程
            FileInputStream fis = new FileInputStream(file);
            ObjectInputStream ois = new ObjectInputStream(fis);
            SingleTon st1 = (SingleTon) ois.readObject();

            ois.close();
            fis.close();
            System.out.println(st1 == singleTon);
        }

        catch (Exception e) {
            e.printStackTrace();
        }

    }

}

打印出来的是false

public class Client {

    public static void main(String[] args) {
        // TODO Auto-generated method stub


        SingletonMenu singleTon = SingletonMenu.singleTon;


        File file = new File("/Users/qianxin/Desktop/tmp.txt");
        try {
            file.createNewFile();


            FileOutputStream fos = new FileOutputStream(file);
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(singleTon);
            oos.flush();
            oos.close();
            fos.close();


            FileInputStream fis = new FileInputStream(file);
            ObjectInputStream ois = new ObjectInputStream(fis);
            SingletonMenu st1 = (SingletonMenu) ois.readObject();

            ois.close();
            fis.close();
            System.out.println(st1 == singleTon);
        }

        catch (Exception e) {
            e.printStackTrace();
        }

    }

}

打印出来的是true

2)可以使用反射强行调用私有构造器(如果要避免这种情况,可以修改构造器,让它在创建第二个实例的时候抛异常)。

public static void main(String[] args) {
        // TODO Auto-generated method stub
        Class<?> singleTonClass = SingleTon.class;
        for (int i = 0; i < 10; i++) {
            SingleTon singleTon;
            try {
                Constructor cons = singleTonClass.getDeclaredConstructor(null);
                cons.setAccessible(true);
                singleTon = (SingleTon) cons.newInstance(null);
                System.out.println(singleTon);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }
    }

打印:

cn.xports.singleton.SingleTon@8391b0c
cn.xports.singleton.SingleTon@5d1eb50b
cn.xports.singleton.SingleTon@b0014f0
cn.xports.singleton.SingleTon@325e9e34
cn.xports.singleton.SingleTon@61e481c1
cn.xports.singleton.SingleTon@6102d81c
cn.xports.singleton.SingleTon@1ba4806
cn.xports.singleton.SingleTon@6cce82cc
cn.xports.singleton.SingleTon@69ed56e2
cn.xports.singleton.SingleTon@5ce345c2

而枚举类很好的解决了这两个问题,使用枚举除了线程安全和防止反射调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值