目录
1.概念
2.饿汉模式
2.1概念
2.2代码实例
3.懒汉模式
3.1概念
3.2代码示例
3.2.1解决懒汉线程安全问题——加锁
3.2.2 解决懒汉线程安全问题——优化加锁方法
1.概念
在软件开发过程中有很多常见的问题场景,设计模式就是为了解决这些这些问题而形成的特定的解决思路。用固定的思路来实现代码,就可以轻松地解决问题。
单例模式就是常用的设计模式之一。
单例模式能保证某个类在程序中只存在唯一一份实例,而不会创建出多个实例。(在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,创建一个对象,其他调用时共享着一个对象。)
单例模式的具体实现方式有两种:“饿汉”和“懒汉”
2.饿汉模式
2.1概念
在类加载的同时就已经创建好了该单例对象,等待被程序使用。在程序调用时直接返回该单例对象。
2.2代码实例
public class Singleton {
//类成员变量
private static Singleton instance=new Singleton();
//重写无参构造方法
private Singleton(){
}
//对外提供一个类成员变量的方法
public static Singleton getInstance(){
return instance;
}
}
因为饿汉模式在类创建时就创建了一个单例模式供系统使用,且不会被改变。所以饿汉模式天生就是线程安全的。
3.懒汉模式
3.1概念
懒汉模式创建对象时,先判断该对象是否有被实例化。如果已经被实例化就直接返回该实例对象,如果没有先进行实例化操作。
3.2代码示例
public class SingletonLazy {
//定义一个类的成员变量
private static SingletonLazy instance=null;
//私有化构造方法
private SingletonLazy(){
}
//对外提供一个获取单例对象的方法
public static SingletonLazy getInstance(){
//判断需要返回的对象是否为空
if (instance==null){
//创建
instance=new SingletonLazy();
}
//返回
return instance;
}
}
SingletonLazy通过将构造方法用private修饰能够防止该类在外部被实例化。在同一个虚拟机范围内,SingletonLazy的唯一实例只能通过getInstance()方法访问。
但是,这种方法是线程不安全的。当两个线程同时判断该实例对象为空,就会创建两个实例化对象。
//演示懒汉式单例模式的线程不安全情况
public class Demo_Singleton03 {
public static void main(String[] args) {
//多个线程并发获取单例模式
for (int i=0;i<10;i++){
//10个线程
Thread thread=new Thread(()->{
//获取单例
SingletonLazy instance=SingletonLazy.getInstance();
System.out.println(instance);
});
thread.start();
}
}
}
3.2.1解决懒汉线程安全问题——加锁
可以给方法加锁,也可以给类对象加锁。
//给方法加锁
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
加锁避免了创建多个实例化对象的问题,但是还会有另一个问题:每次去创建对象时都要去获取锁,并发性能非常地差,极端情况下,可能会出现卡顿现象。
3.2.2 解决懒汉线程安全问题——优化加锁方法
为了优化加锁方法,我们需要实现的是:当没有实例化对象时获取锁创建实例化对象;如果有实例化对象时,不获取锁直接返回实例化对象。
class Singleton {
private static volatile Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
使用双重 if 判定, 降低锁竞争的频率.;给 instance 加上了 volatile.
外层的if是为了判断实例是否被创建。volatile避免了“内存可见性”问题。
1.当三个线程同时执行getInstance方法时,当经过第一层if时都收到了实例没有被创建的消息,于是开始竞争锁。
2.假如线程一获取到锁到达第二层if时,发现实例也没有被创建于是创建了实例,最后释放了锁。
3.当线程一释放了锁,线程二和线程三无论哪一方竞争到了锁到达第一个if时,发现实例已经被创建就不会再去获取锁了。
DCL——Double Check Lock 可以实现在第一次创建时实现同步。所谓的双重校验锁,就是在锁的内部和外部分别对instance进行为null的校验。