单例模式-java

单例模式是 Java 中最简单的设计模式之一。

特点:

(一)只能有一个实例
(二)必须自己创建自己的唯一实例
(三)需要给其他对象使用提供这一实例

创建方式:

(1)饿汉式:类加载时就会创建该单例对象。(不使用也会一直占用内存)
(2)懒汉式:类加载不会创建对象,首次使用该对象时才会创建。(需注意控制线程安全问题)

具体实现单例模式代码:

(1-1)饿汉式:静态成员变量

public class Singleton {
    //构造方法私有化
    private Singleton() {
    }
    private static Singleton instance = new Singleton();
    public static Singleton getInstance() {
        return instance;
    }
}

(1-2)饿汉式:静态代码块

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

(1-3)饿汉式:枚举

public enum  Singleton {
    INSTANCE;
}

饿汉式三种创建方法总结:

1、静态成员变量、静态代码块两种方式没有太大区别,在类加载时会执行创建对象方法。
2、枚举是推荐的实现方式,枚举类型是线程安全的,只会装载一次,枚举是所有单例实现中唯一一种不会被破坏的单例实现模式。(下面会介绍破坏单例的方式和解决方法)

(2-1)懒汉式:(线程不安全)

public class Singleton {
    private Singleton() {
    }
    private static Singleton instance;
    public static Singleton getInstance() {
        if (instance == null) {
            //线程1执行到此处开始等待
            // 线程2获取到cpu执行权,也会执行到此处
            //多个线程执行到此处,进而会创建多个对象
            instance = new Singleton();
        }
        return instance;
    }
}

(2-2)懒汉式:(同步方法锁,线程安全)

public class Singleton {
    private Singleton() {
    }
    private static Singleton instance;
    //单例绝大部分都是读操作,读操作是线程安全的
    //没必要每个线程必须持有锁才能调用该方法,性能低下
    //调整锁的时机,所以有了新的实现模式,双重检查锁(2-3)方式  
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

(2-3)懒汉式:(双重检查锁,线程安全)

public class Singleton {
    private Singleton() {
    }
    //多线程情况下,可能会出现空指针问题
    //原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
    //在变量属性前用volatile关键字修饰,可以保证可见性和有序性
    private static volatile Singleton instance;
    public static Singleton getInstance() {
        //第一次判断 如果!=null 不需要抢占锁 直接返回对象 进而提升效率
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

(2-4)懒汉式:(静态内部类,线程安全)

/**
*由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化静态属性
*/
public class Singleton {
    private Singleton() {
    }
    private static class SingletonHolder {
        //final修饰 防止外部类对它进行修改
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

懒汉式三种创建方法总结:

1、当不涉及多线程调用时,可以使用(2-1)不加同步锁的方式进行创建
2、当多线程操作时,可以使用(2-2)、(2-3)的实现方式。推荐使用(2-3)双重检查锁的方式,更加高效。
3、更加推荐的是(2-4)静态内部类的实现方式,它是一种优秀的单例模式, 是开源项目中比较常用的单例模式。不需要使用任何锁,也保证了多线程安全,并且不影响性能,也不浪费空间。

破坏单例模式

可以理解为通过一定的方式,使单例类可以创建多个对象,(枚举方式除外)。有两种方式,分别是反射序列化

1、反射

首先我们看一下通过反射创建实例的代码

 //获取字节码对象
 Class clazz = Singleton.class;
 //获取无参的构造方法
 Constructor cons = clazz.getDeclaredConstructor();
 //取消访问检查
 cons.setAccessible(true);
 //创建Singleton对象
 Singleton instance1 = (Singleton) cons.newInstance();
 Singleton instance2 = (Singleton) cons.newInstance();

通过打印instance1instance2地址可以得知这是两个不同的对象。那我可以在无参私有的构造方法中进行处理,在类中添加一个静态变量进行标记是否是第一调用构造方法,在第二次调用时我们抛异常处理。如下代码:(双重检查锁举例)

public class Singleton {
    private static boolean flag;
    private Singleton() {
        if (flag) {
            throw new RuntimeException("不允许创建多个对象");
        }
        flag = true;
    }
    private static volatile Singleton instance;
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

2、序列化

这里我们以静态内部类的单例模式进行举例

public class Singleton implements Serializable {
    private Singleton(){}
    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
    //当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回,解决序列化反序列化单例被破坏问题
  //  public Object readResolve(){
  //      return SingletonHolder.INSTANCE;
  // }
}
public static  void writeObjToFile() throws Exception {
    //将对象序列化之后 写入到文件中
    Singleton singleton = Singleton.getInstance();
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("***/a.txt"));
    oos.writeObject(singleton);
    oos.close();
}
public static void readObjFromFile() throws Exception{
    //从文件中读取对象
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("***/a.txt"));
    Singleton singleton = (Singleton) ois.readObject();
    System.out.println(singleton);
    ois.close();
}

首先我们的单例需实现Serializable接口,然后再调用writeObjToFile方法将单例Singleton对象写入到文件中。然后再多次调用readObjFromFile读取对象,经过打印对象地址发现每次取出的对象不是同一个,所以该单例被破坏
原因在ObjectInputStream 的readObject()方法,这里jdk源码以截图形式说明:(由于截图不全,大家可以根据代码行数自行查看)

image.png
image.png
image.png

从这几个主要方法中可以得知,我们需要在Singleton类的readResolve方法中,将我们实例返回即可,也就是我上面代码中注释掉的readResolve方法。这时我们再进行读写操作时,就会得到同一个实例。

总结

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容