• 多线程笔记


    单例创建实例, 网上有很多的例子, 我这里也只是做一下笔记. 可能并不比别人的详细. 主要是为了自己做点云笔记.

    1. 饿汉式

    public class Ehan  {
        //1. 提供一个静态实例
        private final static Ehan instance = new Ehan();
    
        //2. 私有化构造函数
        private Ehan(){}
    
        //提供一个对外获取实例的方法
        public  static Ehan getInstance(){        return instance;
        }
    }

    测试:

    public static void main(String[] args) {
        final CyclicBarrier barrier = new CyclicBarrier(100);
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                try {
                    barrier.await();
                    System.out.println(Thread.currentThread().getName() + " 被唤醒。。。");
                    Ehan obj = Ehan.getInstance();
                    System.out.println(obj.hashCode());
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
            }, "thread" + i).start();
        }
    
        System.out.println("当前线程数 : " + barrier.getParties());
    }

    结果:

     所有的 hashcode 都是一样的, 说明是同一个实例.

    优点: 线程安全的

    缺点: 类加载的时候, 就完成实例化了(如使用了该类的其他静态属性或静态方法, 就会完成实例化, 但事实上, 我可能并不需要他实例化). 如果后面我并不使用这个类, 那不是浪费了么.

    2. 懒汉式

    public class LanHan {
        //1. 定义一个静态变量
        private static LanHan instance;
    
        //2. 私有化构造函数
        private LanHan(){}
    
        //3. 提供一个对外获取实例的方法
        public synchronized static LanHan getInstance(){
            if(instance == null){            
                instance = new LanHan();
            }
            return  instance;
        }
    }

    getInstance() 上的 synchronized 不能省, 省了可能会出现问题.

    测试方法仍然用上面的测试方法, 只是把类改一下就行了.

      对 getInstance() 方法进行修改, 干掉 synchronized , 并在创建前面加个休眠, 模拟干点别的操作, 耗费了点时间.

    public static LanHan getInstance(){
        if(instance == null){
            try {
                Thread.sleep(500);
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new LanHan();
        }
        return  instance;
    }

    结果: 

     

    那这里出现了不同结果, 发现并没有创建单例, 而是创建了不同的实例出来了.

    这是由于方法上没有加锁, 不同线程都能进来, 然后很多线程在休眠哪里停住了, 后面的线程追上来, 也在休眠.(这里其实造成了线程堆积)

    当休眠完成后, 继续执行代码, 就创建实例了.

    懒模式的优缺点:

    优点:

    1. 克服了饿模式的加载创建问题, 当调用 getInstance() 方法的时候, 才会去创建实例. 相当于是按需加载.

    2. 线程安全.

    缺点:

    1. 每次调用 getInstance() 时, 都会进行 为空判断. 

    2. getInstance() 方法加了锁, 并发调用时, 需要排队, 当一个线程释放锁后, 其他线程需要对锁竞争.

    3.  双重判断

    /**
     * double checked locking
     **/
    public class DCL {
    
        //1. 提供一个静态引用
        private static volatile DCL instance;
    
        //2. 私有化构造函数
        private DCL(){}
    
        //3. 提供一个对外的获取实例接口
        public static DCL getInstance(){
            if(instance == null){
                synchronized (DCL.class){
                    if(instance == null){
                        instance = new DCL();
                    }
                }
            }
            return instance;
        }
    }

    结果:

     

     在锁里面再判断一次, 保证了线程安全.

     同样的, 对这里的 getInstance() 方法进行一个小修改:

    public static DCL getInstance() {
        if (instance == null) {
            try {
                Thread.sleep(500);
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (DCL.class) {
                // if(instance == null){
                instance = new DCL();
                // }
            }
        }
        return instance;
    }

    这里, 我将 锁里面的判断注释掉, 并在锁外面加个睡眠, 运行起来看一下:

     

    同样的道理,

    第1条线程进来时, 为空√, 进入休眠

    第2条线程进来时, 为空√, 进入休眠. 第1条线程可能还在休眠, 更不可能把实例创建出来了

    ......

    一些逻辑处理, 可能需要时间, 创建一个实例的时候, 可能并不仅仅是要new出来, 可能之前要做一些逻辑处理, 会消耗一点时间, 跟这里的睡眠效果比较像.

    所以要在锁里面, 再加一个为空判断. 因为锁里面的代码, 只能一个线程进去执行, 所以即使再进行别的逻辑处理, 耗费时间, 也不会出现以上情况.

    直到这条线程创建完毕之后, 别的线程再进来时, 就能判断到实例已创建.

    4. 内部静态类方式

    public class Inner {
        //1. 私有化构造函数
        private Inner(){}
    
        //2. 静态类
        private static class InnerBuilder{
            private final static Inner instance = new Inner();
        }
    
        //3. 提供一个获取实例的方法
        public static Inner getInstance(){
            return InnerBuilder.instance;
        }
    }

    私有化构造函数这步, 在所有的方法中, 都是不能省的, 不然可以通过new的方式来创建, 就不是单例了.

    结果:

    这种方式是推荐使用的方式.

    优点: 

    1. 线程安全

    2. 代码简单

    3. 不用加锁 (有一个初始化锁, 但不是人为加入的)

    4. 延迟加载(内部类只有被外部类加载时, 才会进行加载) 

    5. 枚举类

    public enum EnumObj {
        INSTANCE;
    }

    枚举被认为是常量。

    也有类的功能, 里面可以写方法, 属性。

    一般情况下, 使用内部静态类的方式就行了.

  • 相关阅读:
    linux下oracle启动关闭
    win10安装JDK详细教程
    Spring MVC中用@ResponseBody转json,对json进行处理方法汇总
    js实现横向跑马灯效果
    Oracle的ORA-02292报错:违反完整性约束,已找到子记录
    echarts中legend如何换行
    java中split特殊符号
    Tomcat开启SSL协议支持
    Oracle获取表字段名,字段类型,字段长度,注释
    Oracle根据符合条件的数据循环批量更新
  • 原文地址:https://www.cnblogs.com/elvinle/p/12354362.html
Copyright © 2020-2023  润新知