单例(Singleton)模式:单例模式是一种创建型模式(将对象的创建与使用分离),是指某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例。
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
饿汉式:
/**
* 单例模式
* 饿汉式
* 是否 Lazy 初始化:否
* 是否多线程安全:是
* 实现难度:易
* 描述:这种方式比较常用,但容易产生垃圾对象。
* 优点:没有加锁,执行效率会提高。
* 缺点:类加载时就初始化,浪费内存。
*
* @author Cocowwy
* @create 2020-09-09-10:57
*/
public class SingletonDemo1 {
private static SingletonDemo1 instance = new SingletonDemo1();
private SingletonDemo1() {
}
public static SingletonDemo1 getInstance() {
return instance;
}
}
class SingletonDemo1Main {
public static void main(String[] args) {
//只能通过静态方法拿到 而不能通过私有化的构造器拿
SingletonDemo1 instance = SingletonDemo1.getInstance();
System.out.println(instance);
}
}
通过代码我们可以看出,在类加载的时候就new SingletonDemo1();这样就避免了懒汉式的getInstance()加锁的缺点,保证了线程的安全,但是在类加载的时候就进行了初始化,会浪费内存。
私有化构造器,暴露一个公有的方法让外界访问同一个对象,该对象是在类加载的时候就创建好了(可以理解饥饿,迫不及待想要创建),所以叫做饿汉式。
懒汉式 不安全:
/**
* 单例模式
* 懒汉式
* 是否 Lazy 初始化:是
* 是否多线程安全:否
* 实现难度:易
*
* @author Cocowwy
* @create 2020-09-09-11:09
*/
public class SingletonDemo2 {
private static SingletonDemo2 instance;
public SingletonDemo2() {
}
public static SingletonDemo2 getInstance() {
if (null == instance) {
instance = new SingletonDemo2();
}
return instance;
}
}
class SingletonDemoMain2{
public static void main(String[] args) {
System.out.println(SingletonDemo2.getInstance());
}
}
通过代码我们可以看到,在类加载的时候并未给instance赋值,并且SingletonDemo2 的实例对象只能通过SingletonDemo2 .getInstance()得到,外部是不能通过new SingletonDemo2 ()来得到实例对象的,因为构造器被private 私有化了,只能在内部访问,但是这种方式会存在并发的问题。
私有化构造器,暴露一个公有的方法让外界访问同一个对象,该对象是在需要的时候才创建(可以理解为偷懒,不需要不创建),所以叫做懒汉式。
懒汉式 安全:
/**
* 单例模式
* 懒汉式
* 是否 Lazy 初始化:是
* 是否多线程安全:是
* 实现难度:易
*
* @author Cocowwy
* @create 3030-09-09-11:09
*/
public class SingletonDemo3 {
private static SingletonDemo3 instance;
public SingletonDemo3() {
}
public static synchronized SingletonDemo3 getInstance() {
if (null == instance) {
instance = new SingletonDemo3();
}
return instance;
}
}
class SingletonDemoMain3 {
public static void main(String[] args) {
System.out.println(SingletonDemo3.getInstance());
}
}
在懒汉式的基础上对getInstance加上了锁,解决了并发的问题,这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
静态内部类:
/**
* 单例模式
* 静态内部类
* 是否 Lazy 初始化:是
* 是否多线程安全:是
* 使⽤类的静态内部类实现的单例模式,既保证了线程安全有保证了懒加载,同时不会因为加锁的⽅
* 式耗费性能。
*
* @author Cocowwy
* @create 2020-09-09-11:27
*/
public class SingletonDemo4 {
private static class SingletonDemo4Holder {
private static SingletonDemo4 instance = new SingletonDemo4();
}
private SingletonDemo4() {
}
public static SingletonDemo4 getInstance() {
return SingletonDemo4Holder.instance;
}
}
class SingletonDemo4Main{
public static void main(String[] args) {
System.out.println(SingletonDemo4.getInstance());
}
}
这种方式是SingletonDemo4一旦被加载,但是instance并不会被初始化,因为SingletonDemo4Holder类没有被主动使用,只能显式的调用getInstance方法时,才会显示的装载SingletonDemo4Holder 类,从而实例化instance。
双重锁校验:
/**
* 单例模式
* 双重锁校验(线程安全)
* 是否 Lazy 初始化:是
* 是否多线程安全:是
*
* @author Cocowwy
* @create 2020-09-09-11:38
*/
public class SingletonDemo5 {
private volatile static SingletonDemo5 instance;
public SingletonDemo5() {
}
public static SingletonDemo5 getInstance() {
if (null != instance) {
return instance;
}
synchronized (SingletonDemo5.class) {
if (null == instance) {
instance = new SingletonDemo5();
}
}
return instance;
}
}
class SingletonDemo5Main {
public static void main(String[] args) {
System.out.println(SingletonDemo5.getInstance());
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + SingletonDemo5.getInstance());
}, "Thread" + i).start();
}
}
}
volatile关键字,保证可见性,可以防止jvm指令重排优化。
为什么要先进行一次判空?
第一次校验:也就是第一个if(null != instance),这个是为了代码提高代码执行效率,由于单例模式只要一次创建实例即可,所以当创建了一个实例之后,再次调用getInstance方法就不必要进入同步代码块,不用竞争锁。直接返回前面创建的实例即可。
第二次校验:因为volatile 不能保证原子性,即可能出现当Thread1线程修改后并未写会主内存后,Thread进入到了同步代码块的情况。
本人上述代码git地址:Java设计模式