单例模式

定义

保证一个类仅有一个实例,并提供一个全局访问点

类型

创建型

适用场景

想保证任何情况下都绝对只有一个实例

优点

  • 在内存里只有一个实例,减少了内存开销
  • 可以避免对资源的多重占用
  • 设置全局访问点,严格控制访问

缺点

没有接口,扩展困难

代码

懒汉式单例

public class LazySingleton {

    private static LazySingleton lazySingleton = null;

    private LazySingleton() {
    }

    public static LazySingleton getLazySingleton() {
        if (lazySingleton == null) {
            // 多线程时,当线程1走到这一步时,线程2走到了上一步判断,
            // 因为这一步还没有走完,所以线程2也会进入这一步,因此线程是不安全的
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

测试

public class Test {
    public static void main(String[] args) throws InterruptedException {
        LazySingleton lazySingleton = LazySingleton.getLazySingleton();
        System.out.println(lazySingleton);
        LazySingleton lazySingleton2 = LazySingleton.getLazySingleton();
        System.out.println(lazySingleton);
    }
}

结果

lazySingleton为NUll,创建了lazySingleton
com.honor.design.pattern.creational.singleton.LazySingleton@14ae5a5
com.honor.design.pattern.creational.singleton.LazySingleton@14ae5a5

懒汉式注重延迟加载,只有使用的时候才开始初始化,不使用不会初始化,且单线程中只会实例化一次。
接下来是多线程测试

public class LazySingleton {

    private static LazySingleton lazySingleton = null;

    private LazySingleton() {
    }

    public static  LazySingleton getLazySingleton(int num) throws InterruptedException {
        if (lazySingleton == null) {
            Thread.sleep(num);
            lazySingleton = new LazySingleton();
            System.out.println("lazySingleton为Null,需要初始化");
        }
        return lazySingleton;
    }

}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread T1 = new Thread(() -> {
            LazySingleton lazySingleton = LazySingleton.getLazySingleton(100);
            System.out.println(Thread.currentThread().getName() + ":" + lazySingleton);
        });
        Thread T2 = new Thread(() -> {
            LazySingleton lazySingleton = LazySingleton.getLazySingleton(50);
            System.out.println(Thread.currentThread().getName() + ":" + lazySingleton);

        });
        ExecutorService service = Executors.newFixedThreadPool(5);
        service.submit(T1);
        service.submit(T2);
        Thread.sleep(400);
        service.shutdownNow();
    }
}

结果

lazySingleton为Null,需要初始化
pool-1-thread-2:com.design.pattern.creational.singleton.LazySingleton@5840f74e
lazySingleton为Null,需要初始化
pool-1-thread-1:com.design.pattern.creational.singleton.LazySingleton@278b7126

可以看出,在多线程的情况下,创建了多个单例,如果线程更多,就会创建更多,而且,还会出现多个不一样的结果

修改方案

  1. 使用synchronized
public class LazySingleton {
    private static LazySingleton lazySingleton = null;
    private LazySingleton() {
    }
    // 在静态方法中使用锁,等于锁住了当前的类
    public static synchronized LazySingleton getLazySingleton(int num)  {
        if (lazySingleton == null) {
            try {
                Thread.sleep(num);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

运行结果

lazySingleton为Null,需要初始化
pool-1-thread-2:com.honor.design.pattern.creational.singleton.LazySingleton@56cc009c
pool-1-thread-1:com.honor.design.pattern.creational.singleton.LazySingleton@56cc009c

加锁后,只实例化了一次,且结果都是同一个实例。
但是使用synchronized是锁住了类,而且加锁解锁时,性能都有所消耗。

  1. DoubleCheck双重检查
public class LazyDoubleCheckSingleton {
    // 添加volatile ,使得线程的时候,CPU也可以共享内存,
    //所有线程都可以看到共享内存的最新状态,保证了内存的可见性,从而避免的重排序
    private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;

    private LazyDoubleCheckSingleton() {
    }

    public static LazyDoubleCheckSingleton getInstance() {
        if (lazyDoubleCheckSingleton == null) {
            synchronized (LazyDoubleCheckSingleton.class) {
                if (lazyDoubleCheckSingleton == null) {
                    lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
                    // 1.分配内存
                    // 2和3的位置可能会被重排序造成颠倒,
                    //java允许那些在单线程程序内不影响程序运行结果的重排序
                    // 2.初始化对象
                    // 3.设置lazyDoubleCheckSingleton指向刚刚分配的内存地址
                }
            }
        }
        return lazyDoubleCheckSingleton;
    }
}

测试

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread T1 = new Thread(() -> {
            LazyDoubleCheckSingleton lazySingleton = LazyDoubleCheckSingleton.getInstance();
            System.out.println(Thread.currentThread().getName() + ":" + lazySingleton);
        });
        Thread T2 = new Thread(() -> {
            LazyDoubleCheckSingleton lazySingleton = LazyDoubleCheckSingleton.getInstance();
            System.out.println(Thread.currentThread().getName() + ":" + lazySingleton);

        });
        ExecutorService service = Executors.newFixedThreadPool(5);
        service.submit(T1);
        service.submit(T2);
        Thread.sleep(100);
        service.shutdownNow();
    }
}

结果

lazyDoubleCheckSingleton为Null,需要初始化
pool-1-thread-2:com.design.pattern.creational.singleton.LazyDoubleCheckSingleton@8606d85
pool-1-thread-1:com.design.pattern.creational.singleton.LazyDoubleCheckSingleton@8606d85

因为使用双重判断,只有在示例为空的时候,才会去获取锁,避免了锁的重复获取和释放,减小内存消耗,同时保证了数据一致性。

3.基于类初始化的延迟加载

public class StaticInnerClassSingleton {
    private StaticInnerClassSingleton(){

    }
    private static class InnerClass {
        private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
    }
    public static StaticInnerClassSingleton getInstance() {
        return InnerClass.staticInnerClassSingleton;
    }
}

测试

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread T1 = new Thread(() -> {
            StaticInnerClassSingleton singleton = StaticInnerClassSingleton.getInstance();
            System.out.println(Thread.currentThread().getName() + ":" + singleton);
        });
        Thread T2 = new Thread(() -> {
            StaticInnerClassSingleton singleton = StaticInnerClassSingleton.getInstance();
            System.out.println(Thread.currentThread().getName() + ":" + singleton);

        });
        ExecutorService service = Executors.newFixedThreadPool(5);
        service.submit(T1);
        service.submit(T2);
        Thread.sleep(100);
        service.shutdownNow();
    }
}

结果

pool-1-thread-2:com.design.pattern.creational.singleton.StaticInnerClassSingleton@11438314
pool-1-thread-1:com.design.pattern.creational.singleton.StaticInnerClassSingleton@11438314

当多线程去获取类的初始化锁的时候,只有一个线程可以获得,当一个线程获取到锁后,执行类初始化,对于静态内部类来讲,即便存在重排序,对其他线程也是不可见的,

饿汉式单例

public class HungrySingleton {
    private final static HungrySingleton hungrySingleton;
    static {
        hungrySingleton = new HungrySingleton();
    }
    private HungrySingleton() {

    }
    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }

}

写法简单,类加载的时候就完成了初始化,避免了线程同步问题。
但也因为在类加载的时候完成了初始化,没有延迟加载的效果,如果这个类从始至终都没有被调用过,就造成了内存的浪费。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注