zoukankan      html  css  js  c++  java
  • 多线程笔记

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

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

    枚举被认为是常量。

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

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

  • 相关阅读:
    Azure 虚拟机安全加固整理
    AzureARM 使用 powershell 扩容系统磁盘大小
    Azure Linux 云主机使用Root超级用户登录
    Open edX 配置 O365 SMTP
    powershell 根据错误GUID查寻错误详情
    azure 创建redhat镜像帮助
    Azure Powershell blob中指定的vhd创建虚拟机
    Azure Powershell 获取可用镜像 PublisherName,Offer,Skus,Version
    Power BI 连接到 Azure 账单,自动生成报表,可刷新
    Azure powershell 获取 vmSize 可用列表的命令
  • 原文地址:https://www.cnblogs.com/elvinle/p/12354362.html
Copyright © 2011-2022 走看看