单例模式

来源:互联网 发布:安卓访问服务器数据库 编辑:程序博客网 时间:2024/06/07 05:55

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变量被赋值的时候对象已经是初始化过的,从而避免了上面说到的问题。

其实我觉得还可以这样,不知道我自己理解的对不对,希望高手指教:

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 single = new SingleTon();                    singleTon = single;                }            }        }        return singleTon;    }}

(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();            // Student对象序列化过程            FileOutputStream fos = new FileOutputStream(file);            ObjectOutputStream oos = new ObjectOutputStream(fos);            oos.writeObject(singleTon);            oos.flush();            oos.close();            fos.close();            // Student对象反序列化过程            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@8391b0ccn.xports.singleton.SingleTon@5d1eb50bcn.xports.singleton.SingleTon@b0014f0cn.xports.singleton.SingleTon@325e9e34cn.xports.singleton.SingleTon@61e481c1cn.xports.singleton.SingleTon@6102d81ccn.xports.singleton.SingleTon@1ba4806cn.xports.singleton.SingleTon@6cce82cccn.xports.singleton.SingleTon@69ed56e2cn.xports.singleton.SingleTon@5ce345c2

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

原创粉丝点击