对 volatile 变量的写操作,不允许和它之前的读写操作打乱顺序;
对 volatile 变量的读操作,不允许和它之后的读写乱序。
public class Single {
private static volatile Single s= null;
private Single(){}//私有构造方法,避免外部创建实例
public static Single getInstance() {
Single st = s; // 在这里创建临时变量
if (st== null) {
synchronized (Single.class) {//静态同步函数的锁是(类.class)
st= s;
if (st== null) {
s=st= new Single();
}
}
}
return st; // 注意这里返回的是临时变量
}
这里为什么需要再定义一个临时变量st?通过前面的对 volatile 关键字作用解释可知,访问 volatile 变量,需要保证一些执行顺序,所以的开销比较大。这里定义一个临时变量,在 s 不为空的时候(这是绝大部分的情况),只要在开始访问一次 volatile 变量,返回的是临时变量。如果没有此临时变量,则需要访问两次,而降低了效率。通过这样修改以后,这样能提高 25% 的性能。wiki
关于单例模式,还有一个更有趣的实现,采用静态内部类,它能够延迟初始化(lazy initialization),并且多线程安全,还能保证高性能,如下:
class Singleton{
private Singleton(){}//私有构造方法,避免外部创建实例
private static class SingletonHolder
{
public static final Singleton instance= new Singleton();
}
public static Singleton getInstance()
{
return SingletonHolder.instance;
}
}
利用内部类延迟初始化,这里是利用了 Java 的语言特性,内部类只有在使用的时候,才回去加载,从而初始化内部静态变量。关于线程安全,这是 Java 运行环境自动给你保证的,在加载的时候,会自动隐形的同步。在访问对象的时候,不需要同步 Java 虚拟机又会自动给你取消同步,所以效率非常高。
在反射的作用下,单例结构是会被破坏的,测试代码如下所示
package test;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import singleton.LazySingleton2;
/**
* @author zhengrongjun
*/
public class LazySingleton2Test {
public static void main(String[] args) {
//创建第一个实例
LazySingleton2 instance1 = LazySingleton2.getInstance();
//通过反射创建第二个实例
LazySingleton2 instance2 = null;
try {
Class clazz = LazySingleton2.class;
Constructor cons = clazz.getDeclaredConstructor();
cons.setAccessible(true);
instance2 = cons.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
//检查两个实例的hash值
System.out.println("Instance 1 hash:" + instance1.hashCode());
System.out.println("Instance 2 hash:" + instance2.hashCode());
}
}
输出如下
Instance 1 hash:1694819250
Instance 2 hash:1365202186
根据哈希值可以看出,反射破坏了单例的特性,因此懒汉式V3版诞生了:
package singleton;
public class LazySingleton3 {
private static boolean initialized = false;
private LazySingleton3() {
synchronized (LazySingleton3.class) {
if (initialized == false) {
initialized = !initialized;
} else {
throw new RuntimeException("单例已被破坏");
}
}
}
static class SingletonHolder {
private static final LazySingleton3 instance = new LazySingleton3();
}
public static LazySingleton3 getInstance() {
return SingletonHolder.instance;
}
}
此时再运行一次测试类,出现如下提示
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at test.LazySingleton3Test.main(LazySingleton3Test.java:21)
Caused by: java.lang.RuntimeException: 单例已被破坏
at singleton.LazySingleton3.(LazySingleton3.java:12)
... 5 more
Instance 1 hash:359023572
这里就保证了,反射无法破坏其单例特性.
在分布式系统中,有些情况下你需要在单例类中实现 Serializable 接口。这样你可以在文件系统中存储它的状态并且在稍后的某一时间点取出。让我们测试这个懒汉式V3版在序列化和反序列化之后是否仍然保持单例。
先将
public class LazySingleton3
修改为
public class LazySingleton3 implements Serializable
上测试类如下
package test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import singleton.LazySingleton3;
public class LazySingleton3Test {
public static void main(String[] args) {
try {
LazySingleton3 instance1 = LazySingleton3.getInstance();
ObjectOutput out = null;
out = new ObjectOutputStream(new FileOutputStream("filename.ser"));
out.writeObject(instance1);
out.close();
//deserialize from file to object
ObjectInput in = new ObjectInputStream(new FileInputStream("filename.ser"));
LazySingleton3 instance2 = (LazySingleton3) in.readObject();
in.close();
System.out.println("instance1 hashCode=" + instance1.hashCode());
System.out.println("instance2 hashCode=" + instance2.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出如下
instance1 hashCode=2051450519
instance2 hashCode=1510067370
显然,我们又看到了两个实例类。为了避免此问题,我们需要提供 readResolve() 方法的实现。readResolve()代替了从流中读取对象。这就确保了在序列化和反序列化的过程中没人可以创建新的实例。
因此,我们提供懒汉式V4版代码如下
package singleton;
import java.io.Serializable;
public class LazySingleton4 implements Serializable {
private static boolean initialized = false;
private LazySingleton4() {
synchronized (LazySingleton4.class) {
if (initialized == false) {
initialized = !initialized;
} else {
throw new RuntimeException("单例已被破坏");
}
}
}
static class SingletonHolder {
private static final LazySingleton4 instance = new LazySingleton4();
}
public static LazySingleton4 getInstance() {
return SingletonHolder.instance;
}
private Object readResolve() {
return getInstance();
}
}
此时,在运行测试类,输出如下
instance1 hashCode=2051450519
instance2 hashCode=2051450519
这表示此时已能保证序列化和反序列化的对象是一致的.