Java设计模式----单例模式

单例模式 : 主要作用是保证在整个Java项目中, 该类只有一个实例对象存在; 单例模式的实现方式有多种, 主要以饿汉式加载和懒汉式加载进行举例;

一, 饿汉式加载

    饿汉式加载是在类加载器对该类进行加载的时候就进行对象初始化, 对象在初始化的时候, 访问线程还没有开启, 因此不存在线程问题; 但是饿汉式加载无论该对象在项目运行时是否会被使用, 都会进行对象初始化, 因此容易造成内存浪费;

单例类 : 

public class HungrySingle {

    private static HungrySingle hungrySingle = new HungrySingle();

    private HungrySingle() {}

    public static HungrySingle createInstance() {
        return hungrySingle;
    }

}

测试类 :

 public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(20);
        for(int i = 0; i < 20; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        countDownLatch.await();
                        HungrySingle single = HungrySingle.createInstance();
                        System.out.println(single);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                }
            }, "线程 : " + i).start();
            countDownLatch.countDown();
        }
    }

测试结果 : 


二, 懒加载式 : 

    懒加载式的单例模式, 是在第一次调用该类的时候, 进行该类对象的初始化过程; 在初始化方法中, 需要对当前对象进行判断, 如果已经初始化过, 直接返回初始化对象, 如果没有进行初始化, 进行对象初始化; 因为在初始化过程中, 存在对象初始化的分支判断, 所以懒加载方式的单例模式存在线程安全问题, 在高并发条件下不能保证对象实例的绝对单一化

1, 懒加载 ---- 线程不安全

* 实例创建类

public class LazySingle {

    private static LazySingle lazySingle = null;

    private LazySingle() {}

    public static LazySingle createInstance() {
        if(null == lazySingle) {
            lazySingle = new LazySingle();
            return lazySingle;
        } else {
            return lazySingle;
        }
    }

}

* 测试类

public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(20);

        for(int i = 0; i < 20; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        countDownLatch.await();
                        LazySingle instance = LazySingle.createInstance();
                        System.out.println(instance);
                    }catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
            countDownLatch.countDown();
        }
    }

* 测试结果


多运行几次后会发现, 在打印出来的实例地址中, 存在其他实例; 这是因为CPU在分配内存资源时, 有两个线程同时抢到(null == single)分支, 对象初始化后存在其他的对象地址, 下面我们来进行线程同步;

2, 懒加载 ---- 线程安全_同步方法

    线程方法同步很简单, 在原方法名称上添加关键字synchronized

public synchronized static LazySingle createInstance() {
        if(null == lazySingle) {
            lazySingle = new LazySingle();
            return lazySingle;
        } else {
            return lazySingle;
        }
    }

    进行多次运行后, 单例模式运行正常, 看起来线程安全问题得到解决;

    但是, 线程方法同步在高并发情况下存在一定的性能问题; 添加同步关键字后, 并发情况下对象创建, 会进行队列处理, 前一个线程释放同步锁后, 后一个对象才能拿到该锁进行线程访问; 这样避免了在非线程安全情况下同时抢到空对象的可能, 但是如果对象创建是一个比较复杂的过程, 容易让后面的线程产生长时间的等待, 体验相对较差; 这样可以使用同步代码块进行避免, 直接用同步代码块的双重判空进行处理;

3, 懒加载 ---- 线程安全_同步代码块双重判空处理

public static LazySingle createInstance() {
        if(null == lazySingle) {
            synchronized (LazySingle.class) {
                if(null == lazySingle) {
                    lazySingle = new LazySingle();
                }
            }
        }
        return lazySingle;
    }

    这种方式首先避免了线程不安全问题, 不可能同时存在多个线程抢到第二层的非空校验; 同时也解决了同步方法产生的性能问题, 通过第一次校验, 能同时抢到(null == single)分支的线程本就极少, 这样需要排队等待的线程相对只有有限的几道线程, 对整个流程影响几乎可以忽略不计

3, 懒加载 ---- 线程安全_静态内部类实现

    静态内部类是在当所属的类第一次被引用时进行加载, 并且只加载一次, 这样避免了饿汉式加载造成的内存浪费和同步代码块/同步方法造成的一定程度上的线程等待; 

public class StaticInnerSingle {

    private StaticInnerSingle() {}

    public static StaticInnerSingle createInstance() {
        return InnerSingle.single;
    }

    static class InnerSingle {
        public static final StaticInnerSingle single = new StaticInnerSingle();
    }
}
    

猜你喜欢

转载自blog.csdn.net/u011976388/article/details/80213608