java实现23种设计模式之单例模式

单例模式就是全局对象只有一个,比如开发中spring中常常使用到的bean;

跟静态类有点类似,但是静态类局限很多,不能被继承等。

单例模式又分为饿汉模式和懒汉模式。

饿汉模式是在加载类的时候就创建了实例,不管这个实例能不能用的到;

懒汉模式则是延时加载,用到的时候再创建实例。但是线程不安全。

饿汉模式:

package com.ceshi;

public class Singleton {
    //1.将构造方法私有化,不允许外部直接创建对象
    private Singleton(){        
    }
    
    //2.创建类的唯一实例,使用private static修饰
    private static Singleton instance=new Singleton();
    
    //3.提供一个用于获取实例的方法,使用public static修饰
    public static Singleton getInstance(){
        return instance;
    }
    
    public Integer getNum() {
        return num;
    }

    public void setNum(Integer num) {
        this.num = num;
    }

    private Integer num=0;
}
package com.ceshi;

public class Test {
    public static void main(String[] args) {
        //饿汉模式
        Singleton s1=Singleton.getInstance();
        s1.setNum(2);
        Singleton s2=Singleton.getInstance();
        System.out.println(s2.getNum());
        if(s1==s2){
            System.out.println("s1和s2是同一个实例");
        }else{
            System.out.println("s1和s2不是同一个实例");
        }
    }
}

输出的是同一个实例,且num为2,所有属性被共用。

懒汉模式:

package com.ceshi;
/*
 * 懒汉模式
 * 区别:饿汉模式的特点是加载类时比较慢,但运行时获取对象的速度比较快,线程安全
 *      懒汉模式的特点是加载类时比较快,但运行时获取对象的速度比较慢,线程不安全
 */
public class Singleton2 {
    //1.将构造方式私有化,不允许外边直接创建对象
    private Singleton2(){
    }
    
    //2.声明类的唯一实例,使用private static修饰
    private static Singleton2 instance;
    
    //3.提供一个用于获取实例的方法,使用public static修饰
    public static Singleton2 getInstance(){
        if(instance==null){
            instance=new Singleton2();
        }
        return instance;
    }
}
package com.ceshi;

public class Test {
    public static void main(String[] args) {
        
        //懒汉模式
        Singleton2 s3=Singleton2.getInstance();
        Singleton2 s4=Singleton2.getInstance();
        if(s3==s4){
            System.out.println("s3和s4是同一个实例");
        }else{
            System.out.println("S3和s4不是同一个实例");
        }
    }
}

结果是同一个实例。

 测试线程问题,防止电脑性能好,加一点东西

package com.ceshi;
/*
 * 懒汉模式
 * 区别:饿汉模式的特点是加载类时比较慢,但运行时获取对象的速度比较快,线程安全
 *      懒汉模式的特点是加载类时比较快,但运行时获取对象的速度比较慢,线程不安全
 */
public class Singleton2 {
    //1.将构造方式私有化,不允许外边直接创建对象
    private Singleton2(){
    }
    
    //2.声明类的唯一实例,使用private static修饰
    private static Singleton2 instance;
    
    //3.提供一个用于获取实例的方法,使用public static修饰
    public static Singleton2 getInstance(){
        if(instance==null){
            try {
                // 模拟在创建对象之前做一些准备工作
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance=new Singleton2();
        }
        return instance;
    }
}
package com.ceshi;

public class Test {
    public static void main(String[] args) {
        
        //懒汉模式
        Thread2[] threads = new Thread2[10];
        /*for (Thread2 thread2 : threads) {
            thread2 = new Thread2();
            thread2.start();
        }*/
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread2();
            threads[i].start();
        }
    }
}
//测试线程
class Thread2 extends Thread {
 @Override
 public void run() {
     System.out.println(Singleton2.getInstance().hashCode());
 }
}

测试结果显示

完全不一样

如何解决?首先会想到对getInstance方法加synchronized关键字

public static synchronized Singleton2 getInstance(){
        if(instance==null){
            try {
                // 模拟在创建对象之前做一些准备工作
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance=new Singleton2();
        }
        return instance;
    }

 

结果又变得一样了。

这样的用法,在性能上会有所下降,因为每次调用getInstance(),都要对对象上锁,事实上,只有在第一次创建对象的时候需要加锁,之后就不需要了,需要进行改进。

package com.ceshi;
/*
 * 懒汉模式
 * 区别:饿汉模式的特点是加载类时比较慢,但运行时获取对象的速度比较快,线程安全
 *      懒汉模式的特点是加载类时比较快,但运行时获取对象的速度比较慢,线程不安全
 */
public class Singleton2 {
    //1.将构造方式私有化,不允许外边直接创建对象
    private Singleton2(){
    }
    
    //2.声明类的唯一实例,使用private static修饰
    private static Singleton2 instance;
    
    private static synchronized void syncInit() {
        if (instance == null) {
            instance = new Singleton2();
        }
    }

    public static Singleton2 getInstance() {
        if (instance == null) {
            syncInit();
        }
        return instance;
    }
}

这样就只创建了一次实例

除非使用枚举类型的单例模式,否则其他模式都可以通过反射机制调用私有的构造方法。

package com.ceshi;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * 单例模式被java反射攻击
 * @author Administrator
 *
 */
public class SingletonReflectAttack {
    public static void attack() throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException
     {
         Class<?> classType = Singleton2.class;
         Constructor<?> constructor = classType.getDeclaredConstructor(null);
         constructor.setAccessible(true);
         Singleton2 singleton = (Singleton2) constructor.newInstance();
         Singleton2 singleton2 = Singleton2.getInstance();
         System.out.println(singleton == singleton2);  //false
     }
}
package com.ceshi;

import java.lang.reflect.InvocationTargetException;

public class SingletonReflectAttackMain {

    public static void main(String[] args) throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException {
        System.out.println("-------------单例模式被java反射攻击测试--------------");
        SingletonReflectAttack.attack();
        System.out.println("--------------------------------------------------");
    }
}

结果:

返回结果为false,说明创建了两个不同的实例。通过反射获取构造函数,然后调用setAccessible(true)就可以调用私有的构造函数;所以创建出来的两个实例时不同的对象。

如果要抵御这种攻击,就要修改构造器,让他在被要求创建第二个实例的时候抛出异常。

package com.ceshi;
/*
 * 懒汉模式
 * 区别:饿汉模式的特点是加载类时比较慢,但运行时获取对象的速度比较快,线程安全
 *      懒汉模式的特点是加载类时比较快,但运行时获取对象的速度比较慢,线程不安全
 */
public class Singleton2 {
    //1.将构造方式私有化,不允许外边直接创建对象
    private Singleton2(){
        synchronized (Singleton2.class) {
            if(false == flag)
            {
                flag = !flag;
            }
            else
            {
                throw new RuntimeException("单例模式正在被攻击");
            }
        }
    }
    
    //2.声明类的唯一实例,使用private static修饰
    private static Singleton2 instance;
    private static boolean flag = false;
    
    private static synchronized void syncInit() {
        if (instance == null) {
            instance = new Singleton2();
        }
    }
    
    public static Singleton2 getInstance() {
        if (instance == null) {
//            syncInit();
            synchronized (Singleton2.class) {
                if (instance == null) {
                    //双重校验
                    instance = new Singleton2();
                }
            }
        }
        return instance;
    }
}

在私有的构造方法中再设置同步锁:

抛出异常,如果需要防止此类事件发生,可以通过捕获异常解决问题。

posted @ 2019-04-12 18:30  十黎九夏  阅读(291)  评论(0编辑  收藏  举报