• 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;
        }
    }

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

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

  • 相关阅读:
    内存使用信息及cpu使用信息
    网站被攻击了怎么办
    seo 百度不收录
    php 使用功能
    sl 动态调用wcf
    php 项目中遇到的问题 ...
    Closures
    php 配置虚拟主机
    jQery 常用工具大全
    jquery基础使用!
  • 原文地址:https://www.cnblogs.com/zhengyuanyuan/p/10697791.html
Copyright © 2020-2023  润新知