您的位置:首页 > 编程语言 > Java开发

java写单例模式

2016-04-21 11:22 387 查看

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开发中并不推荐

总之,不管采用何种方案,要保证三点:

- 线程安全

- 延迟加载

- 序列化与反序列化安全
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java