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

Java单例模式几种写法

2015-05-29 12:19 337 查看
/**
*
* @author Fernando
*    饿汉式单例
*/
public class Singleton {
private static Singleton ins = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return ins;
}
}

饿汉式提前实例化,没有懒汉式中多线程问题,但不管我们是不是调用getInstance()都会存在一个实例在内存中。

采用内部类式单例类:

public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
    return SingletonHolder.INSTANCE;  
    }  
}
内部类式中,实现了延迟加载,只有我们调用了getInstance(),才会创建唯一的实例到内存中.并且也解决了懒汉式中多线程的问题.解决的方式是利用了Classloader的特性.由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;

package singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
*
* @author Fernando 懒汉式单例
*/
public class Singleton1 {
private static Singleton1 ins;
private static int count = -1;

private Singleton1() {

System.out.println("count:" + (++count));

}

public static synchronized Singleton1 getInstance1() {//注意!这里如果不使用同步方法,当两个线程A和B同时进到这个函数,
//就会生成两个实例对象,因为此时ins == null。
if (ins == null)
ins = new Singleton1();
return ins;
}

//这样写程序不会出错,因为整个getInstance1是一个整体的"critical section",但就是效率很不好,
//因为我们的目的其实只是在第一个初始化instance的时候需要locking(加锁),而后面取用instance的时候,根本不需要线程同步

/**
* 使用单例提供的getInstance()方法只能得到同一个单例,除非是使用反射方式,将会得到新的单例
*/
public static void main(String[] args) throws NoSuchMethodException,
SecurityException, InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
Singleton1 ins = Singleton1.getInstance1();
Singleton1 ins1 = Singleton1.getInstance1();

try {
Class c = Class.forName(Singleton1.class.getName());
Constructor ct = c.getDeclaredConstructor();
ct.setAccessible(true);
Singleton1 ins2 = (Singleton1) ct.newInstance();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

双检锁写法:

public class Singleton{
private static Singleton single;    //声明静态的单例对象的变量
private Singleton(){}    //私有构造方法

public static Singleton getSingle(){    //外部通过此方法可以获取对象
if(single == null){
synchronized (Singleton.class) {   //保证了同一时间只能只能有一个对象访问此同步块
if(single == null){
single = new Singleton();
}
}
}
return single;   //返回创建好的对象
}
}


这段代码看起来很完美,很可惜,它是有问题。主要在于
single = new Singleton()
这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。

给 instance 分配内存
调用 Singleton 的构造函数来初始化成员变量
将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。

我们只需要将 instance 变量声明成 volatile 就可以了。

public class Singleton {
private volatile static Singleton single; //声明成 volatile
private Singleton (){}

public static Singleton getSingleton() {
if (single == null) {
synchronized (Singleton.class) {
if (single == null) {
single = new Singleton();
}
}
}
return instance;
}

}


有些人认为使用 volatile 的原因是可见性,也就是可以保证线程在本地不会存有 instance 的副本,每次都是去主内存中读取。但其实是不对的。使用 volatile 的主要原因是其另一个特性:禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完 1-2-3 之后或者 1-3-2 之后,不存在执行到 1-3 然后取到值的情况。从「先行发生原则」的角度理解的话,就是对于一个 volatile
变量的写操作都先行发生于后面对这个变量的读操作(这里的“后面”是时间上的先后顺序)。(这段参考自Jark’s blog)

用枚举写单例实在太简单了!这也是它最大的优点。下面这段代码就是声明枚举实例的通常做法。

public enum EasySingleton{
INSTANCE;
}

我们可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: