单例模式能够保证一个类仅有唯一的实例,并提供一个全局访问点。
我们是不是可以通过一个全局变量来实现单例模式的要求呢?我们只要仔细地想想看,全局变量确实可以提供一个全局访问点,但是它不能防止别人实例化多个对象。通过外部程序来控制的对象的产生的个数,势必会系统的增加管理成本,增大模块之间的耦合度。所以,最好的解决办法就是让类自己负责保存它的唯一实例,并且让这个类保证不会产生第二个实例,同时提供一个让外部对象访问该实例的方法。自己的事情自己办,而不是由别人代办,这非常符合面向对象的封装原则。
单例模式主要有3个特点:
实现单例的饿汉模式主要有3步:
private SingletonHunger() {}
public static SingletonHunger instance = new SingletonHunger();
public static void main(String[] args) {
SingletonHunger s1 = SingletonHunger.instance;
SingletonHunger s2 = SingletonHunger.instance;
System.out.println(s1 == s2); // true
}
private static SingletonHunger instance = new SingletonHunger();
public static SingletonHunger getInstance() {
return instance;
}
饿汉模式有什么特点呢,可以看到最明显的就是instance是静态成员变量,它在类被加载的时候就会被实例化,不管有没有被其它外部类访问,所以这种一开始就实例化好的,我们觉得它很着急,所以叫饿汉模式。
public class SingletonHunger {
// 1. 将默认的构造函数私有化
private SingletonHunger() {}
// 2. 提供静态变量
private static SingletonHunger instance = new SingletonHunger();
// 3. 创建instance变量的getter访问器
public static SingletonHunger getInstance() {
return instance;
}
}
懒汉模式和饿汉模式的却别就在于懒汉模式只是声明类的实例变量,而在有外部类(线程)访问的时候才去真正实例化(开辟内存空间),之后的所有线程都共享最先创建的那个实例
public class SingletonLazy {
// 1. 将默认的构造函数私有化
private SingletonLazy() {
}
// 2. 声明类的唯一实例 只是声明
private static SingletonLazy instance;
// 3. 为instance提供访问器
public static SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
饿汉模式:类加载时较慢,访问对象时较快,线程安全
懒汉模式:类加载时较快,访问对象时较慢,线程不安全
大多数应用场景下都使用懒汉模式,因为饿汉模式还有一个问题那便是内存空间的浪费。所以我们就需要解决懒汉模式的线程不安全问题
主要解决的问题是两个
第一个问题我们可以用加锁来实现,用sychronizd即可,第二个问题是Java的关键字volatile。
**加锁:**我们可以直接加在getter上,但是这样子的静态方法锁整个临界区比较大,比较耗费资源,所以使用同步代码块
public static SingletonLazy getInstance() {
if (instance == null) {
synchronized (SingletonLazy.class) {
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
为什么要判空两次?其实就是为了用同步代码块,你必须保证临界区完成一整套必不可少的操作,最开始的判空只是判断是否需要进入临界区。加入两个线程同时停在了第一个判空处,其中一个线程获得锁进去不判空直接new,那么它完成操作释放锁之后对于第二个等待锁的线程而言,它获得一释放的锁之后也是进去直接new,很显然,这一点都不符合临界区的设计。
volatile
instance = new Singleton();
这条语句并不是一个原子操作
再详细的内容参考博客
所以最后我们得到的结果为这样
public class SingletonLazy {
// 1. 将默认的构造函数私有化
private SingletonLazy() {
}
// 2. 声明类的唯一实例 只是声明
private volatile static SingletonLazy instance;
// 3. 为instance提供访问器
public static SingletonLazy getInstance() {
if (instance == null) {
synchronized (SingletonLazy.class) {
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
}
java单例设计模式详解(懒汉饿汉式)+深入分析为什么懒汉式是线程不安全的+解决办法:https://blog.csdn.net/yaoyaoyao_123/article/details/84799861
【JAVA】线程安全的懒汉模式为什么要使用volatile关键字:https://blog.csdn.net/weixin_42078452/article/details/84892372