java写单例模式

java写单例模式

一、最简单方式–饿汉法

在第一次创建该类的时候就创建对象实例,而不管实际是否需要创建。代码如下:

public class Singleton {
    private static Singleton singleton = new Singleton();
    private Singleton() {}
    public static Singleton getSingleton() {
        return singleton;
    }
}

这种做法编写简单,但是并没有实现延迟创建对象,就是不管现在需要不需要我都要创建。

二、单线程的方法

实现延迟加载,开始只定义变量,而不进行创建。在获取时再进行判断,为空时创建;不为空时直接返回。代码如下:

public class Singleton {
    private static Singleton singleton = null;
    private Singleton() {}
    public static Singleton getSingleton() {
        if(singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

但是这种做法有一个最大的问题,就是线程不安全,当多个线程同时调用getSingleton方法时,可能出现多个实例存在

三、考虑线程安全的写法

public class Singleton {
    private static volatile Singleton singleton = null;
    private Singleton() {}
    public static Singleton getSingleton() {
        synchronized(Singleton.class){
            if(singleton == null) {
                signleton = new Singleton();
            }
        }
        return singleton;
    }
}

这种写法考虑了线程安全,将对singleton的null判断以及new的部分使用synchronized进行加锁。同时,对singleton对象使用volatile关键字进行限制,保证其对所有线程可见(一个线程修改,所有线程可同步),并且禁止对其指令进行重排序。

四、兼顾效率和线程安全的方法

上述方法可以在语意上保证线程安全,但是效率并不理想。因为在每次调用getSingleton方式时,都需要在synchronized排队,但是真正需要new的情况并不多。所以,此时就有了下面这种做法:

public class Singleton {
    private static volatile Singleton singleton = null;
    private Singleton() {}
    public static Singleton getSingleton() {
        if(singleton == null) {
            synchronized(Singleton.class) {
                if(singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

在上述方法中,第一个判断是否为空是保证 在不需要创建时,就不用 在synchronized中排队;第二个if判断是为了保证多线程安全的问题。这种方法称为 “多重检查锁”,可以提高并行度


这里顺便学习了一下关键字volatile(在jdk 1.5引入的)是干啥用的,它主要有两层语义作用:
第一层语义就是可见性。可见性指的是在一个线程中对该变量的修改会马上由工作内存(Work Memory)写回主内存(Main Memory),所以会马上反应在其他线程的读取操作中。**工作内存和主内存可以近似理解为实际电脑中的高速缓存和主存,工作内存是线程独享的,主存是线程共享的。
第二层语义是禁止指令重排序优化。我们写的代码(尤其是多线程代码),由于编译器优化,在实际执行的时候可能与我们编写的顺序不同。编译器只保证程序执行结果和源代码相同,却不保证实际指令的顺序与源代码相同。这在单线程中是没有问题的,但是在多线程中,这种乱序就可能导致比较严重的问题。


五、静态内部类法

通过这种方法,把Singleton实例放到一个静态内部类中,这就避免了静态实例在Singleton类加载的时候就创建对象,并且由于静态内部类只会被加载一次,所以这种写法也是线程安全的:

public class Singleton {
    private static class Holder {
        private static Singleton singleton = new Singleton();
    }
    private Singleton() {}
    public static Singleton getSingleton() {
        return Holder.singleton;
    }
}

但是,上面提到的所有实现方式都有两个共同的缺点:都需要额外的工作(Serializable、transient、readResolve())来实现序列化,否则每次反序列化一个序列化的对象实例时都会创建一个新的实例。
可能会有人使用反射强行调用代码中的私有构造器(如果要避免这种情况,可以修改构造器,让它创建第二个实例时抛出异常)


六、枚举写法

当然,有一种更为优雅的方法来实现单例模式,枚举写法

public enum Singleton {
    INSTANCE;
    private String name;
    public String getName(){
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

使用这种写法,除了线程安全和防止反射强行调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。因此Effective java推荐尽可能的使用枚举来实现单例。但是这中写法会耗费双倍的内存,所以Android开发中并不推荐


总之,不管采用何种方案,要保证三点:
- 线程安全
- 延迟加载
- 序列化与反序列化安全

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值