zoukankan      html  css  js  c++  java
  • 单例的几种写作方式和序列化

    2015-09-02 16:02:07

    推荐一篇大神的文章,好久之前就看过这篇文章,而且本文的思路就来自这篇文章~其实有了大牛的文章,本文可写可不写,但是为了给自己总结一下,就写了~

    http://callmegod.iteye.com/blog/1474441

    单例的作用想必大家都知道,也一直在用,但是到底怎样的单例是“比较”完美的单例模式呢?我也不知道,但是我一直在寻找答案~

    下面我简单总结一下单例吧,否则太乱了

    Part 1:单例的几种实现方式

    1. 恶汉式:指在类加载或者被初始化的时候,就创建一个单例对象,有好几个变种

    第一种:类加载时创建对象

    1 public class SingletonObj {
    2     public static final SingletonObj INSTANCE = new SingletonObj();
    3 
    4     private SingletonObj() {}
    5 }

    第二种:类加载时创建对象,但是对象是私有的,只能通过static的方法获取,好像更加“面向对象”

    1 public class SingletonObj {
    2     private static final SingletonObj INSTANCE = new SingletonObj();
    3 
    4     private SingletonObj() {}
    5 
    6     public static SingletonObj getInstance() {
    7         return INSTANCE;
    8     }
    9 }

    第三种:类初始化的时候创建对象

     1 public class SingletonObj {
     2     public static SingletonObj INSTANCE;
     3 
     4     static {
     5         INSTANCE = new SingletonObj();
     6     }
     7 
     8     private SingletonObj() {}
     9 
    10     private static SingletonObj getInstance() {
    11         return INSTANCE;
    12     }
    13 }

    2. 懒汉式:使用时才创建对象,相比于恶汉式,这种方式会延迟加载。

    第一种:线程不安全

     1 public class SingletonObj {
     2     public static SingletonObj INSTANCE;
     3 
     4     private SingletonObj() {}
     5 
     6     private static SingletonObj getInstance() {
     7         if (INSTANCE == null) {
     8             INSTANCE = new SingletonObj();
     9         }
    10         return INSTANCE;
    11     }
    12 }

    第二种:线程安全,同时支持双重检查机制,这种也是最常见的单例模式(这种方式不推荐)

     1 public class SingletonObj {
     2     private static final Object sLocker = new Object();
     3     public static SingletonObj INSTANCE;
     4 
     5     private SingletonObj() {
     6     }
     7 
     8     private static SingletonObj getInstance() {
     9         if (INSTANCE == null) {
    10             synchronized (sLocker) {
    11                 if (INSTANCE == null) {
    12                     INSTANCE = new SingletonObj();
    13                 }
    14             }
    15         }
    16         return INSTANCE;
    17     }
    18 }

    这种方式有自己的弊端:有种说法,说是jdk1.5后才能保证达到单例效果,但是据我所知,这种方式来自C语音的双重检查机制,在Java中不见得能完全适用。

    所以保险的加锁方式如下:

     1 public class SingletonObj {
     2     public static SingletonObj INSTANCE;
     3 
     4     private SingletonObj() {
     5     }
     6 
     7     private synchronized static SingletonObj getInstance() {
     8         if (INSTANCE == null) {
     9             INSTANCE = new SingletonObj();
    10         }
    11         return INSTANCE;
    12     }
    13 }

    3. 静态内部类方式:加锁的方式能做到线程安全,但是加锁会导致效率比较低,而这种方式既能满足线程安全的要求,而且效率比较高

     1 public class SingletonObj {
     2     private static class SingletonHolder {
     3         public static final SingletonObj INSTANCE = new SingletonObj();
     4     }
     5 
     6     private SingletonObj() {
     7     }
     8 
     9     private static SingletonObj getInstance() {
    10         return SingletonHolder.INSTANCE;
    11     }
    12 }

    主要利用到静态类只会被JVM加载一次的特性~所以即便是多线程,也不会产生两个对象。这也是一种比较优雅的方式。

    4. 《Effective Java》中提到一种更好的方法:单元素的枚举类型,大体实现如下(极棒的方式)

     1 public enum SingletonEnum {
     2     INTANCE(12, "Niu");
     3 
     4     private int age;
     5     private String name;
     6     SingletonEnum(int age, String name) {
     7         this.age = age;
     8         this.name = name;
     9     }
    10 
    11     public void doSomething() {
    12         System.out.println("SingletonEnum doSomething");
    13     }
    14 }

    好处:线程安全,序列化/反序列化安全

    弊端:失去了类的一些特性,因此不够流行

    Part 2:

    1. 上面提到了序列化,我们都知道,所谓序列化/反序列化就是对象和流之间的一种转换方式。因为对象是不能直接保存在硬盘或者通过网络传输的,但是序列化成流之后,原则上你想干嘛都行。但是单例的序列化比较特殊,我们知道,单例的作用就是保证JVM中只有一个对象,那么如何保证反序列化后的对象和序列化之前的是同一个对象呢?

     1 public class SingletonCase implements Serializable {
     2     private static class SingltonHolder {
     3         public static final SingletonCase INSTANCE = new SingletonCase();
     4     }
     5 
     6     private int age;
     7     private String name;
     8     public static SingletonCase getInstance() {
     9         return SingltonHolder.INSTANCE;
    10     }
    11 
    12     private SingletonCase() {
    13         age = 133;
    14         name = "David";
    15     }
    16 
    17     public int getAge() {
    18         return age;
    19     }
    20 
    21     public String getName() {
    22         return name;
    23     }
    24 
    25     private Object readResolve() {
    26         return SingltonHolder.INSTANCE;
    27     }
    28 }

    需要添加如上绿色代码,这是一个方法,返回值必须是Object,而不能是自己定义的对象。这个方法的作用是:每次反序列化完成前都会去调用readResolve()方法,将用原对象取代新创建出来的对象,这样就保证了序列化前后对象的唯一性。

    单例的线程安全和序列化一直是个比较难缠的安全问题,所以单元素的枚举类型实现单例,既能保证线程安全性,又能保证序列化安全,可谓是一种perfect的方案~~

    2. 关于transient

     如果你不想某个成员变量被序列化,那么就可以用这个关键字来修饰~具体的使用大家问度娘。

  • 相关阅读:
    如何测试私有/受保护的方法? (译文)
    推荐一款vs.net中的版本号管理工具,
    ILMerge 合并多个程序集为一个.
    https://XXX/.xml Error:800C000E
    an easy way to debug windows service in .net
    提高安全性, 删除IIS中的response http header
    我的<程序人生>的一点看法.
    vs.net 2005 beta2 之痛
    ANT打包
    Linq to xml: XDocument对象
  • 原文地址:https://www.cnblogs.com/wlrhnh/p/4778978.html
Copyright © 2011-2022 走看看