单例模式
来源:互联网 发布:安卓访问服务器数据库 编辑:程序博客网 时间: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
而枚举类很好的解决了这两个问题,使用枚举除了线程安全和防止反射调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。
- 单例、单例模式
- 单例模式-多线程单例模式
- 单件模式(单例模式)
- 设计模式------单例模式
- 设计模式------单例模式
- 设计模式-单例模式
- 设计模式 - 单例模式
- 设计模式---单例模式
- 设计模式---单例模式
- PHP模式-单例模式
- 【设计模式】单例模式
- 设计模式-单例模式
- 设计模式----单例模式
- 设计模式--单例模式
- 设计模式-单例模式
- 单例模式(单子模式)
- 设计模式-单例模式
- [设计模式] 单例模式
- logback的使用和logback.xml详解
- opencv3 Mat类的成员函数forEach
- 【Algorithm】 着色
- java适配器模式
- postgresql9.6.0
- 单例模式
- 用pyinstaller打包发布含pyqt5模块的程序
- LightOJ 1118 树状数组+离线
- gcc工具链& arm-linux-gcc交叉工具链
- Elasticsearch索引迁移的三种方式
- 高德地图----逆向地理编码(将经度纬度转换地址)
- 自动化测试(python+selenium)入门(二)
- 解决点击EditText不弹出键盘,但现实光标闪烁的问题
- List使用add方法添加数据时的覆盖问题