加锁的单例模式
public class ThreadSingleton {
/**
* 定义静态属性使用volatile关键字
* volatile关键字修饰变量来解决无序写入产生的问题,因为volatile关键字的一个重要作用是即使在多线程环境下,也可以禁止指令重排序,
* 即保证不会出现内存分配、返回对象引用、初始化这样的顺序,从而使得双重检测真正发挥作用
*/
private static volatile ThreadSingleton instance;
static String LOCK = "lock";
private ThreadSingleton(){
}
// 锁的是静态类本身,影响并发性能
// public static synchronized ThreadSingleton getInstance(){
// if(null == instance){
// instance = new ThreadSingleton();
// }
// return instance;
// }
// 每次获取实例都要判空,性能不好
public static ThreadSingleton getInstance(){
if(null == instance){
synchronized(LOCK){
//再次判断,因为可能两个线程同时到达同步块之前,防止多次实例化
if(null == instance){
instance = new ThreadSingleton();
}
}
}
return instance;
}
}
错误和正确的静态变量单例模式
public class StaticSingleton {
// 错误写法instance没有声明为final,可以被置空
//private static StaticSingleton instance = new StaticSingleton();
public static void main(String[] args) {
// 如果上面对instance的声明没有final,那么此处会被置空
// StaticSingleton.instance = null;
}
// 无论是否需要都会实例化,占用空间
private static final StaticSingleton instance = new StaticSingleton();
private StaticSingleton(){
}
public static StaticSingleton getInstance(){
return instance;
}
}
静态内部类单例模式
public class StaticInnerSingleton {
// 存在反射 和 序列化问题
private StaticInnerSingleton(){
}
// 静态内部类的加载不需要依附外部类,在使用时才加载,所以不会提前实例化
private static class Inner{
private static final StaticInnerSingleton instance = new StaticInnerSingleton();
}
public static StaticInnerSingleton getInstance(){
return Inner.instance;
}
}
上面是性能不错,但不完全的单例模式,不安全是因为反射、序列化还可以实现多例!!!
使用枚举:单元素的枚举已经成为单例模式实现的最佳方法。
public enum EnumSingleton {
INSTANNCE{
@Override
protected void read() {
System.out.println("read");
}
};
protected abstract void read();
}
注意:上面的方式有abstract方法!!
反射漏洞
Contructor<Singleton> constructor = Singletton.class.getDeclaredContructor();
constructor.setAccessible(true);
Singleton singleton = costructor.newInsatnce();
反射漏洞处理
修改单例类的私有构造器如下:
private Singleton (){
if(instance != null){
throw new RuntimeException();
}
}
反序列化漏洞
Singleton singleton = Singleton.getInstance();
File file = new File("MySingleton.txt");
try {
FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(singleton);
fos.close();
oos.close();
System.out.println(singleton.hashCode());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis);
Singleton rSingleton = (Singleton) ois.readObject();
fis.close();
ois.close();
System.out.println(rSingleton.hashCode());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
反序列化漏洞处理
需要在单例类中增加:
private Object readResolve(){
return instance;//instance为单例对象的引用
}
原理:那么这个readResolve()方法是从哪来的,为什么加上之后就能返回同一实例了呢?
找到ObjectInputStream类的readObject方法中间接调用了readResolve方法,如果有则直接调用自身定义的readResolve方法。
单元素枚举是实现单例模式的最好方式,线程安全、防反射攻击、防止序列化生成新的实例(参考:https://segmentfault.com/a/1190000000699591)
原因如下:
1). 枚举自己处理序列化
但是枚举单例,JVM对序列化有保证。
2) 枚举实例创建是thread-safe
是通过类加载机制来保证的,我们看看INSTANCE的实例化时机,是在static块中,JVM加载类的过程显然是线程安全的
3)对于反射,如果枚举含有abstract方法,反编译后类的修饰abstract,所以没法实例化,反射也无能为力。
附上面枚举类反编译的结果:
public abstract class Singleton extends Enum
{
private Singleton(String s, int i)
{
super(s, i);
}
protected abstract void read();
protected abstract void write();
public static Singleton[] values()
{
Singleton asingleton[];
int i;
Singleton asingleton1[];
System.arraycopy(asingleton = ENUM$VALUES, 0, asingleton1 = new Singleton[i = asingleton.length], 0, i);
return asingleton1;
}
public static Singleton valueOf(String s)
{
return (Singleton)Enum.valueOf(singleton/Singleton, s);
}
Singleton(String s, int i, Singleton singleton)
{
this(s, i);
}
public static final Singleton INSTANCE;
private static final Singleton ENUM$VALUES[];
static
{
INSTANCE = new Singleton("INSTANCE", 0) {
protected void read()
{
System.out.println("read");
}
protected void write()
{
System.out.println("write");
}
};
ENUM$VALUES = (new Singleton[] {
INSTANCE
});
}
}