一、定义
它是一种对象创建模式,用于创建对象的一个具体实例,它可以确保某个类只产生一个实例。
二、目的
(1)节省频繁创建新对象所花费的时间,尤其对于重量级对象而言。
(2)new操作次数减少,对于堆内存的使用也减少,并且有利于减轻垃圾回收的压力。
因此,对于系统中频繁使用的对象,使用单例模式可以有效地改善系统性能。
三、实现
(1)最简单的方式:
//没有实现延迟加载
public class LearnSingleton0 {
private static LearnSingleton0 obj=new LearnSingleton0();
private LearnSingleton0(){
System.out.println("创建实例");
}
public static LearnSingleton0 getInstance() {
return obj;
}
public static void print() {
System.out.println("用于展示,单例对象没有延迟加载");
}
public static void main(String[] args) {
LearnSingleton0.print();
}
}
运行结果:
创建实例
用于展示,单例对象没有延迟加载
首先,构造函数必须是私有的,从而保证,无法在外部,通过new来创建实例。其次,实例变量obj必须是static的,getInstance方法也必须是静态的。
这种方法虽然简单,但是,缺点也很明显,即没有对obj实例做延迟加载,从运行结果即看以看出,当我们调用print函数的时候,我们本不需要创建一个实例,但是它还是创建了。为了提高系统在相关函数调用时的响应速度,我们要引入延迟加载机制:
(2)引入延迟加载:
//延迟加载,但是使用了同步,性能降低
public class LearnSingleton {
private static LearnSingleton obj=null;
private LearnSingleton(){}
public static synchronized LearnSingleton getInstance() {
if(obj==null){
obj=new LearnSingleton();
}
return obj;
}
}
首先,静态成员变量obj置为空,从而确保系统启动时没有额外的负载。其次,getInstance方法中,判断obj是否为空,从而决定是否要创建对象,保证了单例。最后需要注意的是synchronized关键字,令这个方法是同步的,否则在多线程的环境下,当线程1正在创建单例,而还没有对obj完成赋值操作时,线程2可能判断obj为null,从而线程2也将新建一个对象。
虽然,这个方法引入了延迟加载,但是,与此同时也引入了同步关键字,因此在多线程的环境中,它的时间复杂度远远大于第一种单例模式。下面介绍一种更完善的实现方法,既可以做到延迟加载,也不必使用同步关键字:
(3)使用内部类:
//使用内部类,即实现了延迟加载,又能对多线程友好
public class LearnSingleton2 {
private LearnSingleton2(){}
private static class LearnSingletonHolder{
private static LearnSingleton2 obj=new LearnSingleton2();
}
public static LearnSingleton2 getInstance(){
return LearnSingletonHolder.obj;
}
}
四、特例
通常情况下,以上方法可以保证系统中存在单例类唯一的实例。但是,仍然有例外情况,可能导致系统生成单例类的多个实例,下面我们介绍两种情况:
(1)反射机制,强行调用单例类的私有构造函数:
public class LearnSingleton0 {
private static LearnSingleton0 obj=new LearnSingleton0();
private LearnSingleton0(){
System.out.println("创建实例");
}
public static LearnSingleton0 getInstance() {
return obj;
}
public static void print() {
System.out.println("用于展示,单例对象没有延迟加载");
}
public static void main(String[] args) throws Exception {
Class<?> testClass=Class.forName("pkgOne.LearnSingleton0");
Constructor constructor=testClass.getDeclaredConstructor();
LearnSingleton0 newInstance=(LearnSingleton0) constructor.newInstance();
System.out.println("旧实例:"+getInstance());
System.out.println("新实例:"+newInstance);
}
}
通过输出看以看出两个实例的地址不同,是不同的实例。
(2)序列化和反序列化的单例类实例:
//单例模式的序列化和反序列化
public class LearnSingleton3 {
public static void main(String[] args) throws Exception {
Fruit fruit=null;
Fruit fruit2=Fruit.getInstance();
FileOutputStream outputStream=new FileOutputStream("e:/123.txt");
ObjectOutputStream objectOutputStream=new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(fruit2);
objectOutputStream.flush();
objectOutputStream.close();
outputStream.close();
FileInputStream inputStream=new FileInputStream("e:/123.txt");
ObjectInputStream objectInputStream=new ObjectInputStream(inputStream);
fruit=(Fruit)objectInputStream.readObject();
System.out.println("fruit:"+fruit);
System.out.println("fruit2:"+fruit2);
}
}
class Fruit implements Serializable{
private static final long serialVersionUID = 6810712581273812115L;
private Fruit(){
System.out.println("创建单例fruit");
}
private static class FruitHolder{
private static Fruit fruit=new Fruit();
}
public static Fruit getInstance() {
return FruitHolder.fruit;
}
}
运行结果:
创建单例fruit
fruit:pkgOne.Fruit@1de17f4
fruit2:pkgOne.Fruit@b8f8eb
由此可见,经过序列化和反序列化后,得到的是两个对象。为了解决这个问题,我们可以加入一个函数readResolve解决,如下:
public class LearnSingleton3 {
public static void main(String[] args) throws Exception {
Fruit fruit=null;
Fruit fruit2=Fruit.getInstance();
FileOutputStream outputStream=new FileOutputStream("e:/123.txt");
ObjectOutputStream objectOutputStream=new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(fruit2);
objectOutputStream.flush();
objectOutputStream.close();
outputStream.close();
FileInputStream inputStream=new FileInputStream("e:/123.txt");
ObjectInputStream objectInputStream=new ObjectInputStream(inputStream);
fruit=(Fruit)objectInputStream.readObject();
System.out.println("fruit:"+fruit);
System.out.println("fruit2:"+fruit2);
}
}
class Fruit implements Serializable{
private static final long serialVersionUID = 6810712581273812115L;
private Fruit(){
System.out.println("创建单例fruit");
}
private static class FruitHolder{
private static Fruit fruit=new Fruit();
}
public static Fruit getInstance() {
return FruitHolder.fruit;
}
//实现这个方法,从而使readObject形同虚设,从而保证了单例模式经过序列化和反序列化后,
//仍然返回同一个对象实例
private Object readResolve(){
return FruitHolder.fruit;
}
}