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

java单例设计模式总结

2016-04-02 12:07 525 查看
说到设计模式,比较常见的是单例设计模式!

单例模式的特点:每一次实例化的对象都是同一个;

单例模式有两种实现方式:1、饿汉式2、懒汉式

饿汉式,就是还没一开始使用便初始化好。

public class Singleton {
private static final Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance() {
return singleton;
}
}
优点:预加载,避免了线程安全问题。
缺点:还没使用便加载好了,占用内存。

如果想等到程序一加载后再初始化,可以使用懒汉式。

public class Singleton {
private static Singleton singleton = null;
private Singleton(){}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
优点:懒加载,一开始不占用资源。
缺点:会有线程安全问题。

考虑到线程安全问题,可以使用synchronized关键字修饰。

public class Singleton {
private static Singleton singleton = null;
private Singleton(){}
public synchronized static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
这样就解决了线程安全问题,但是由于同步的原因,性能不高,可以做如下优化

public class Singleton {
private static Singleton singleton = null;
private Singleton(){}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
这样从代码的从代码的角度上看就问题,但是从JVM上分析,创建一个对象需要申请分配一块内存初始化构造函数;将指针指向分配的内存。这两个操作的先后顺序,JVM并没有规定好,特别是java编译后会将字节码进行重排序。所以可能会出现这样的情况:线程A先进来创建对象,将指针指向分配的内存,但是未完成初始化完成,线程B进来的时候,由于指针有引用singleton!=null,会直接未完成初始化好的对象,则会出现异常。

为了解决如上问题,可以考虑使用一个临时变量引用,来确保先初始化好再将指针指向内存的顺序,代码如下

public class Singleton {
private static Singleton singleton = null;
private Singleton(){}
public static Singleton getInstance() {
if (singleton == null) {
Singleton temp;
synchronized (Singleton.class) {
temp = singleton;
if (temp == null) {
synchronized(Singleton.class) {
if (temp == null) {
temp = new Singleton();
}
}
singleton = temp;
}
}
}
return singleton;
}
}
问题其实还没完成解决,因为JVM编译优化可能将改变代码的顺序,如singleto = temp;放到同步代码块里面,所以需要重新做处理。

针对以上问题,终极解决方案有两种:

1、使用volatile

在JDK1.5的时候,出现volatile关键字,在并发的时候,当线程A的数据发生变化时,会通知其他线程修改数据这样能有效的避免JVM编译导致的重排序问题,即使出现线程A还没初始化完对象,线程B就进来的情况,也会通过volatile让线程A初始化好后通知线程B变更数据,代码如下:

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


2、使用静态匿名内部类

public class Singleton {
private Singleton(){}
private static class SingletonInstance {
private static final Singleton singleton = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.singleton;
}
}
由于代码没有static的属性,所以当Singl
4000
eton被加载的时,内部类不会初始化,只有当getInstance()被调用时才会加载SingletonInstance,由于使用类加载的形式,并用final修饰,初始化对象的时候具有原子性,所以不要考虑线程安全,所以不需要加锁。

备注:通过反射我们可以强制生成多个对象,这些极端现象我们不做考虑,除了这样的方式,我们还可以使用序列化的方式强制生成多个对象,代码如下:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Singleton implements Serializable {
private static final Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance() {
return singleton;
}
private Object readResolve(){
return singleton;
}
public static void main(String[] args) throws Exception {
Singleton s1 = null;
Singleton s2 = getInstance();
FileOutputStream fos = new FileOutputStream("d:/aa.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("d:/aa.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (Singleton) ois.readObject();
System.out.println(s1.hashCode() == s2.hashCode());
}
}



上面的代码页面打印的结果是true,但是注释掉readResolve函数的时候,打印的结果会是false。

大家看完还有什么不懂或是觉得代码哪里有问题,欢迎留言交流。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: