单例设计模式
使用单例模式的好处:
(1)对于频繁使用的对象,可以减轻不停的创建对象所花费的时间。
(2)由于new的次数减少,从而降低了系统内存的使用频率,同时也减轻了GC的压力,缩短了GC的停顿时间。
单例模式的不同写法
饿汉式
public class Singleton{
private static Singleton instance=new Singleton();
private Singleton(){
System.out.println("创建单例");
}
public static Singleton getInstance(){
return instance;
}
public static void open(){
System.out.println("开始");
}
public static void main(String[] args) {
Singleton.open();
}
}
说明:这种单例实现非常简单有十分可靠,但是它唯一的不足就是无法对instance对象做延迟加载。也就是说在上述代码中,当我们执行Singleton.open()时,会输出“创建单例”,“开始”。我们不需要Singleton对象,但是在执行open()过程中时会将instance进行实例化。所以我们要延迟加载。
懒汉式
public class Singleton{
private static Singleton instance=null;
private Singleton(){
System.out.println("创建单例");
}
public static synchronized Singleton getInstance(){
if(instance==null)
instance=new Singleton();
return instance;
}
public static void open(){
System.out.println("开始");
}
public static void main(String[] args) {
Singleton.open();
}
}
说明:根据懒汉式,我们可以解决延迟加载的问题。但是由于在懒汉式中会出现多线程不友好的问题,所以对getInstance()方法进行了同步处理(饿汉式天生多线程友好,故不需要进行处理)。进行同步处理之后,则调用该方法耗时就长。无形中就降低了程序的性能。
双向校验锁的形式
public class Singleton{
private static Singleton instance = null;
private Singleton(){
System.out.println("创建单例");
}
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance=new Singleton();
}
}
}
return instance;
}
public static void open(){
System.out.println("开始");
}
public static void main(String[] args) {
Singleton.open();
}
}
说明:这是懒汉式的一种表现形式,该操作不再是对整个函数进行加锁,只对存在并发问题的部分代码进行加锁。这样做的好处是改善性能。但是这样做还是会有一点不安全。
volatile关键字的双向校验
public class Singleton{
private volatile static Singleton instance = null;
private Singleton(){
System.out.println("创建单例");
}
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
public static void open(){
System.out.println("开始");
}
public static void main(String[] args) {
Singleton.open();
}
}
说明:这种写法更加强化了线程安全性,当声明instance引用不使用volatile时,可能线程a先执行instance = new Singleton(),但是new对象这个操作并不是原子性的,在new对象的过程分为三步,按执行顺序分别是
1.给对象分配内存空间
2.初始化实例对象
3.将引用instance指向分配的内存空间
因为处理器会优化这三个步骤,使得这三个步骤发生重排序,执行步骤变为了1->3->2。当线程执行完了3时(此时instance引用并不是null,而是指向了1步骤中分配的内存地址。但是,instance对象初始化并没有完成,并不是一个完整的对象),快要执行2步骤,突然cpu切换了资源,那么b线程就进入该方法当中,instance此时是不等于null的,所以直接执行return。此时返回的是一个不完整的对象,可能会造成系统的崩溃
静态内部类的形式
public class Singleton{
private Singleton(){
System.out.println("创建单例");
}
public static void open(){
System.out.println("开始");
}
public static Singleton getInstance(){
return SingletonInner.instance;
}
private static class SingletonInner{
private static Singleton instance=new Singleton();
}
public static void main(String[] args) {
Singleton.open();
}
}
说明:在这个实现中,单例模式使用内部类的形式来维护单例的实例.当Singleton类被加载时,其内部类并不会被初始化,而调用getInstance()函数时,就会对其进行初始化.这种实现很好的兼备了上述两种实现的优点.但是这个实现仍然有例外情况,可能导致系统生成多个实例.如通过反射机制,强行调用单例类的构造方法.这种极端的方式就不做讨论.除了这些极端的方式会破坏实例的单例性,仍然有一些合法的方式对其单例性进行破坏.如串行化.下面举个例子
public class Singleton implements Serializable{
private static Singleton instance=new Singleton();
private Singleton(){
System.out.println("创建单例");
}
public static Singleton getInstance(){
return instance;
}
public static void open(){
System.out.println("开始");
}
// private Object readResolve(){
// return instance;
// }
public static void main(String[] args) throws Exception {
Singleton s=Singleton.getInstance();
//进行串行化
FileOutputStream fos=new FileOutputStream("Singleton.txt");
ObjectOutputStream oos=new ObjectOutputStream(fos);
oos.writeObject(s);
oos.flush();
oos.close();
//进行反串行化
FileInputStream fis=new FileInputStream("Singleton.txt");
ObjectInputStream ois=new ObjectInputStream(fis);
Singleton s1=(Singleton)ois.readObject();
System.out.println("s="+s+",s1="+s1);
}
}
当注释部分代码没有写时,构建测试代码先获取instance对象,再对Singleto进行串行化和反串行化得当的对象进行比较,会发现这两个对象不相等。解决的办法是写上注释部分的代码,这样反序列化过程中readObject()方法就如同虚设,它直接使用readResolve()方法替换了原本的返回值,从而在形式上构造了单例。