如何创建多线程环境下的可序列化单例模式

来源:互联网 发布:r9s怎么清除相机数据 编辑:程序博客网 时间:2024/05/16 17:00

本文从以下几个点讲解:

  1. 实现单例的两种方式(预加载&懒加载)
  2. 多线程环境下的单例
  3. 可序列化的单例
  4. 对以上代码的重构
  5. 单例与枚举

1. 实现单例的两种方式

预加载:

public class Foo {    private static final Foo INSTANCE = new Foo();    private Foo() {        if(INSTANCE != null) {            throw new IllegalStateException("Already instantiated");        }    }    public static Foo getInstance() {        return INSTANCE;    }}

懒加载:

class Foo {    private static Foo INSTANCE = null;    private Foo() {        if(INSTANCE != null) {            throw new IllegalStateException("Already instantiated");        }    }    public static Foo getInstance() {        if(INSTANCE == null) {            INSTANCE = new Foo();        }        return INSTANCE;    }}

2. 多线程环境下的单例

上面的代码在单线程的情况下可以满足需要,如果在多线程环境下,则需要进行修改:

class Foo {    // 请注意volatile关键字    private static volatile Foo INSTANCE = null;    private Foo() {        if(INSTANCE != null) {            throw new IllegalStateException("Already instantiated");        }    }    public static Foo getInstance() {        if(INSTANCE == null) {          // check 1            synchronized(Foo.class) {                if(INSTANCE == null) {  // check 2                    INSTANCE = new Foo();                }            }        }        return INSTANCE;    }}

3. 可序列化的单例

解决了多线程环境下的单例,可以进一步的思考如何实现可序列化的单例。反序列化可以不通过构造函数直接生成一个对象,所以反序列化时,我们需要保证其不再创建新的对象。
关于序列化的详细文章:click me

class Foo implements Serializable {    private static final long serialVersionUID = 1L;    private static volatile Foo INSTANCE = null;    private Foo() {        if(INSTANCE != null) {            throw new IllegalStateException("Already instantiated");        }    }    public static Foo getInstance() {        if(INSTANCE == null) {            synchronized(Foo.class) {                if(INSTANCE == null) {                    INSTANCE = new Foo();                }            }        }        return INSTANCE;    }    @SuppressWarnings("unused")    private Foo readResolve() {        return INSTANCE;    }}

readResolve方法可以保证,即使程序在上一次运行时序列化过此单例,也只会返回全局唯一的单例。

4. 对以上代码重构

public class Foo implements Serializable {    private static final long serialVersionUID = 1L;    // 使用内部静态class实现懒加载    private static class FooLoader {        // 保证在多线程下无差错运行        private static Foo INSTANCE = new Foo();    }    private Foo() {        throw new UnsupportedOperationException("can not construct this Foo");    }    public static Foo getInstance() {        return FooLoader.INSTANCE;    }    @SuppressWarnings("unused")    private Foo readResolve() {        return FooLoader.INSTANCE;    }}

5. 单例与枚举

最后提供一种更加简洁的方法:

public enum Foo {    INSTANCE;}

08年 google 开发者年会中,Joshua Bloch Joshua Bloch 在 高效 Java 话题中解释了这种方法,视频请戳 这里,在他 演讲的ppt 30-32 页提到:

// 实现单例正确的方式如下:public enum Elvis {    INSTANCE;    private final String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" };    public void printFavorites() {        System.out.println(Arrays.toString(favoriteSongs));    }}

在 Effective Java 线上部分 说到:

上述实现单例的方式,其实等同于将 INSTANCE 设置为 public static final 的方式。
不同之处在于,使用枚举的方式显得更为简洁,而且枚举默认提供了序列化机制,也保证了多线程访问的安全
虽然这种单例的实现方式还没有被广泛使用,但是实现单例的最好方式就是使用一个单元素的枚举。

为什么可以这么简洁?

因为 Java 中每一个枚举类型都默认继承了 java.lang.Enum,而 Enum 实现了 Serializable 接口,所以枚举类型对象都是默认可被序列化的。
通过反编译,也可以知道枚举常量本质上是一个pubilc static final xxx

关于序列化的详细文章:click me

阅读全文
0 0
原创粉丝点击