概念
保证指定的类只有一个实例,不能创建出其他的实例
实现方式
1.饿汉模式
1.1 代码展示
java
package 设计模式;
/**
* Created with IntelliJ IDEA.
* Description:
* User: wuyulin
* Date: 2023-07-28
* Time: 11:28
*/
//单例模式(饿汉模式)
//保证Singleton类只有一个实例
//Singleton类中instance对象的创建时机:在Singleton类被jvm加载的时候创建,Singleton类会在第一次使用的时候被加载,不一定是程序一启动的时候
class Singleton{
//带有static的是类属性,由于每个类的类对象是单例的,所以类对象的属性(static)也是单例的
private static Singleton instance=new Singleton();
public static Singleton getSingleton(){
return instance;
} //当多个线程调用getSingleton方法相当于多个线程读取同一个变量,这是线程安全的
//由于要规定在类的外部不能实例化Singleton类型的对象,所以要把构造函数改为私有的,这样就在类外部就不能实例化Singleton类的对象了
private Singleton(){};
}
public class Deom1 {
public static void main(String[] args) {
Singleton s1=Singleton.getSingleton();
Singleton s2=Singleton.getSingleton();
//Singleton s3=new Singleton();
System.out.println(s1==s2);
//System.out.println(s1==s3);
}
}
1.2 Singleton类中instance对象的创建时机
Singleton类中instance对象的创建时机:在Singleton类被jvm加载的时候创建,Singleton类会在第一次使用的时候被加载,不一定是程序一启动的时候
1.3 控制Singleton类的实例只有一个的核心步骤
1.3.1 在类属性(static)中进行创建,带有static的是类属性,由于每个类的类对象是单例的,所以类对象的属性(static)也是单例的
1.3.2 提供一个获取这个对象的接口,方便在类外调用这个对象
1.3.3 由于规定在类的外部不能实例化Singleton类型的对象,所以要把构造函数改为私有的,这样就在类外部就不能实例化Singleton类的对象了
1.4线程安全问题
饿汉模式实现单例模式是没有线程安全问题的,因为多个线程调用getSingleton()方法,相当于多个线程读取instance引用的值,没有涉及到多个线程进行修改,所以没有线程安全问题
2.懒汉模式
2.1代码展示
java
package 设计模式;
/**
* Created with IntelliJ IDEA.
* Description:
* User: wuyulin
* Date: 2023-07-28
* Time: 12:05
*/
//单例线程(懒汉模式)
//SingletonLazy类中instance对象创建的时机:在第一次调用getInstance()方法时创建
class SingletonLazy{
private static volatile SingletonLazy instance=null; //加上volatile避免内存可见性问题
public static SingletonLazy getInstance(){
if(instance==null){ //第一个条件判断是否需要加锁
synchronized(SingletonLazy.class){ //第二个条件用于判断是否要创建对象
if(instance==null){
instance=new SingletonLazy();
}
}
}
return instance;
}
private SingletonLazy(){};
}
public class Demo2 {
public static void main(String[] args) {
SingletonLazy s1=SingletonLazy.getInstance();
SingletonLazy s2=SingletonLazy.getInstance();
System.out.println(s1==s2);
//SingletonLazy s3=new SingletonLazy();
}
}
2.2SingletonLazy类中instance对象创建的时机
在第一次调用getInstance()方法时创建
2.3控制SingletonLazy类的实例只有一个的核心步骤
2.3.1在类属性(static)中进行创建声明,带有static的是类属性,由于每个类的类对象是单例的,所以类对象的属性(static)也是单例的
2.3.2 提供一个实例化这个对象并返回这个对象的接口
2.3.3 由于规定在类的外部不能实例化Singleton类型的对象,所以要把构造函数改为私有的,这样就在类外部就不能实例化Singleton类的对象了
2.4 线程安全问题
懒汉模式存在线程安全问题
当多个线程判断instance=null,可能多个线程都会判断为null值,导致创建多个对象,所以要对getInstance()方法中的代码进行加锁,将判断和创建对象两个操作变成一个原子性的操作
但实际上是需要一直加锁的吗?不是的,只有我们准备实例化instance对象的时候会出现线程安全问题,当创建成功后,instance!=null,此时多个线程都是获得instance引用的值,不存在线程安全问题,也就不需要加锁了,所以要判断一下是否需要加锁,当instance为null表示当前第一次准备创建该对象,此时加锁控制只有第一个线程创建对象,其余线程返回instance的值,instance不为null说明instance对象已经创建成功,多个线程都只是读取instance的值,不存在线程安全问题,所以不用加锁
加了锁以后该程序其实还有内存可见性问题,因为在第一次调用getInstance()方法创建对象后,第二次去读取instance的值,你不一定能读到最新的instance的值,原因请看线程安全问题(内存可见性),所以要在instance变量声明的时候加上volatile(易变的),解决内存可见性问题
使用volatile不仅仅是为了预防出现内存可见性问题,还要解决指令重排序问题,指令重排序问题请看线程安全问题(指令重排序)