关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

多线程实现的那些方法

发布时间:2020-02-24 00:00:00

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

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){for (int i = 0; i < 100; i++) {new Thread(() -> {
                Ehan obj = Ehan.getInstance();
                System.out.println(obj.hashCode());
            }, "thread" + i).start();
        }
}

结果:

 所有的 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;
}

这种方式来保证单例, 我还没有专门去这么干过.

 

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


/template/Home/Zkeys/PC/Static