说起单例肯定会想起,懒汉,饿汉。但是那都不是完美的。
但是还是要说一下的。
首先Singleton 指的就是被实例化一次的类。在spring ioc 容器中初始化bean的时候默认就是单例的。
饿汉
在程序加载的时候开销比较大,如果没有用到很浪费资源
public class ESingleTon(){
private static final ESingleTon instance=new ESingleTon();
private ESingleTon (){}
public static ESingleTon getInstance(){
return instance;
}
}
懒汉
public class LSingleTon(){
private static LSingleTon instance=null;
private LSingleTon(){}
public static LSingleTon getInstance(){
if(instance!=null){
return instance;
}
//这里为什么要加锁,因为在多线程情况下由于上面的判断不是原子性的所以会产生都是null 的判断。为什么不在方法上加内置锁呢。因为 getInstance()会被多次调用,每次都进入锁其他的线程会被阻塞。所以性能不好。
synchronized(LSingleTon.class){
if(instance!=null){
return instance;
}
instance = new LSingleTon();
return instance;
}
}
-------------------------------------------------这是华丽的分割线-------------------------------------------------------
上面是是常用的单例的实现方法,但是为什么说不完美呢。其实在一些情况下并不能保证单例。(序列化,反射)
首先说序列化
@Test
public void testLSingleTon() throws IOException, ClassNotFoundException {
LSingleTon singleTon=LSingleTon.getInstance();
ObjectOutputStream outputStream=new ObjectOutputStream(new FileOutputStream("E:/test/test.class"));
outputStream.writeObject(singleTon);
System.out.println("new: "+singleTon.toString());
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("E:/test/test.class"));
System.out.println("objinput: "+objectInputStream.readObject().toString());
}
class LSingleTon implements Serializable{
private static final long serialVersionUID = 1L;
private static transient LSingleTon instance=null;
private Object readResolve(){
return instance;
}
private LSingleTon(){
System.out.println("constructor: "+this);
}
public static LSingleTon getInstance(){
if(instance!=null){
return instance;
}
//这里为什么要加锁,因为在多线程情况下由于上面的判断不是原子性的所以会产生都是null 的判断。为什么不在方法上加内置锁呢。因为 getInstance()会被多次调用,每次都进入锁其他的线程会被阻塞。所以性能不好。
synchronized(LSingleTon.class){
if(instance!=null){
return instance;
}
instance = new LSingleTon();
return instance;
}
}
}
可以看到 下面两个对象地址是不一样 的。所以单利失败。
那怎么办呢
序列化的类中有一个方法被我注释,只要打开就可以了。
只要序列化和反序列化公用一个jvm 堆栈,那么就可以取到相同的对象。
这样就OK了。
反射破环单例
这个很好理解,只要调用newInstance()就可以常见新的对象。这怎么办?
先看一下newinstance()的源码
这里判断是不是enum 如果是 不能创建enum objects ,反射是无法newInstance()来创建枚举对象的。
那只要是枚举就行了吗。NO
一个枚举类型的类 编译器 编译以后就是一个 class extends Enum{}
可以看到有一个 静态代码块
用来创建这个类的对象的,而且是根据有多少个enum元素就创建多少个对象对象的引用变量就是enum 中定义的元素名。
所以不能有多个enum 元素,只能有一个 就是一个单例子。
像这样 就OK了
public enum LSingleTon{
instance;
…类的其他属性
}