单例模式
一. 单例模式介绍
什么是单例模式及优点:
保证某个类在项目中只存在一个实例,并且该类只提供一个获取其实例的方法,节省系统资源,对需要频繁创建销毁的对象使用单例模式,可以提高系统性能
使用场景
需要频繁创建,销毁的对象,创建时耗费资源较大的,又频繁用到的,例如工具类对象,访问数据库或访问文件的,例如Hibemate的SessionFactory
JDK源码分析单例模式
例如JDK中,java.lang.Runtime使用的就是饿汉式单例模式
二. 单例模式的八种实现方法
1. 饿汉式
实现步骤
- 创建需要单例的类
- 私有化构造器,防止外部通过new创建实例
- 在类的内部创建一个静态的私有化实例(以属性存在)
- 创建一个静态的公共方法,向外暴露类中创建的这个实例
分析优缺点
- 优点: 写法简单,由于在类的内部创建的实例是静态的,所以在该类装载时就完成了实例化,通过classloder机制避免线程同步问题
- 缺点: 在类装载时完成实例化,会出现一个问题,如果此次的系统运行并没有用到这个实例,则会造成内存浪费,
- 分析得出: 在项目中如果要进行实例化,并且该实例在程序运行时一定会用到,可以推荐此方式
代码示例
//饿汉式一: 静态常量
class Singleton{
//1.私有化构造器,不允许通过new创建其他对象
private Singleton(){
}
//2.提供一个私有的静态的实例对象
private final static Singleton instance = new Singleton();
//3.通过公关的静态方法将静态实例对外暴露
//(由于上方创建的实例是静态的,存在静态域中,
//不管外部调用几次该方法,实际上获取到的是同一个实例)
public static Singleton getInstance(){
return instance;
}
}
//饿汉式二: 静态代码块
class Singleton2{
//1.声明一个私有的静态的实例对象,注意此处只是声明
private static Singleton2 instance;
//2.私有化构造器,不允许通过new创建其他对象
private Singleton2(){
}
//3.创建静态代码块,在代码块中对声明的实例进行赋值
static{
instance = new Singleton2();
}
//4.通过公关的静态方法将静态实例对外暴露
//(由于上方创建的实例是静态的,存在静态域中,
//不管外部调用几次该方法,实际上获取到的是同一个实例)
public static Singleton2 getInstance(){
return instance;
}
}
2. 懒汉式一(双重检查式解决线程安全问题)
实现步骤
- 创建需要单例的类
- 在类的内部声明该类的实例(注意只是声明)
- 私有化构造器,防止外部通过new创建实例
- 提供一个公共的静态方法,在方法内判断声明的实例是否为空,当为空时才去创建,将实例暴露
- 注意线程安全问题及效率问题,使用volatile修饰声明的实例,在合适的位置使用synchronized修饰创建实例的代码(代码示例中有分析)
分析优缺点
- 懒汉式加载在需要的时候才去进行实例化,需要考虑线程安全问题,使用synchronized修饰,考虑效率问题,注意synchronized修饰的位置,使用volatile修饰,设置线程可见性,防止引用溢出等
代码示例
//懒汉式: 线程安全问题及synchronized解决线程安全问题
class Singleton3{
//1.声明一个私有的静态的实例对象,注意此处只是声明
private static Singleton3 instance;
//2.私有化构造器,不允许通过new创建其他对象
private Singleton3(){
}
//3.提供公共的静态方法,对外暴露实例,注意点:
//上方的实例只是声明,在方法内部对什么进行实例化
//当有调用到该方法时才进行实例化
public static Singleton3 getInstance(){
//如果声明的instance等于null时才去new
//否则说明已经创建过了直接返回即可
if( null == instance){
//业务代码.....
return new Singleton3();
}
return instance;
}
//分析第3步骤可能存在的问题:
//在多线程的情况下,假设当线程A走到业务代码,进行业务处理或阻塞,
//还没有创建对象时,线程B调用该方法,判断instance实例也是空,
//会再次创建一个实例对象并返回,此时就出现了多个实例对象
//4.解决线程安全问题一:
//使用 synchronized 修饰创建实例并返回的方法,在同一时间内保证只有一个线程执行该方法
public static synchronized Singleton3 getInstance2(){
if( null == instance){
//业务代码.....
return new Singleton3();
}
return instance;
}
//分析第4步,虽然解决了线程安全问题,但是会出现一个问题: 假设该方法在项目中会多次用到
//每当调用该方法时,都需要判断是否有线程正在执行,如果有阻塞等待,效率太低,使用双重检查
}
//懒汉式: 双重检查,防止线程安全问题,及效率低的问题
class Singleton4{
//1.声明一个私有的静态的实例对象,注意此处只是声明
//并且使用volatile进行修饰,设置当前线程对该变量
//进行修改时,其它线程的及时可见性,并防止指令重排,防止反序列化时可能出现的问题,
//下一个线程拿到当前线程未初始化完毕的引用,造成引用溢出等
private static volatile Singleton4 instance;
//2.私有化构造器,不允许通过new创建其他对象
private Singleton4(){
}
//3.提供公共的静态方法,对外暴露实例,注意点:
//上方的实例只是声明,在方法内部对什么进行实例化
//当有调用到该方法时才进行实例化
public static Singleton4 getInstance(){
//如果声明的instance等于null时才去new
//否则说明已经创建过了直接返回即可
if( null == instance){
//4.使用synchronized对创建实例的代码加锁,同一时间内只保证一个线程执行
//注意点: 如果锁加在这个位置,上面声明的instance需要使用volatile修饰
//虽然volatile能设置线程可见性,但并不能保证线程安全
synchronized (Singleton4.class){
//业务代码.....
//5.在锁中再次进行if判断
if( null == instance){
return new Singleton4();
}
}
}
return instance;
}
}
3. 懒汉式二(静态内部类式)
原理及优点
- 通过内部类不会随着外部类的装载而装载,解决了饿汉式的内存浪费
- 通过静态数据类装载机制,在jvm中只有一份,存在静态域中,解决了懒汉式可能存在的线程安全问题
实现步骤
- 创建需要单例的类
- 私有化构造器,防止外部new创建实例
- 在该类中创建一个私有化静态内部类
- 静态内部类中创建私有化的静态的外部类实例
- 外部类中提供公共的方法,该方法通过静态内部类返回外部类的实例
代码示例
//懒汉式: 静态内部类式
class Singleton5{
//1.私有化构造器,不允许通过new创建其他对象
private Singleton5(){
}
//2.创建私有化静态内部类
private static class InnerSingleton{
//3.在内部类中创建私有化的静态的外部类实例
private static Singleton5 INSTANCE = new Singleton5();
}
//3.外部类中提供公共的静态方法,在方法中通过内部类获取该类的实例并返回
public static Singleton5 getInstance(){
return InnerSingleton.INSTANCE;
}
}
4. 通过枚举实现单例模式
原理
网上找的资料慢的不是太明白,不瞎写了,通过枚举的什么特点,解决线程安全问题,保证单例问题,先贴一个示例代码吧
代码示例
//枚举单例模式使用示例
public class SingletonTest {
public static void main(String[] args) {
// 只能通过INSTANCE获取单例对象
SharedInstanceEnum demo = SharedInstanceEnum.INSTANCE;
// 单例对象的成员变量赋值
SharedInstanceEnum.INSTANCE.val2 = 5;
SharedInstanceEnum.INSTANCE.config("AAA");
// 单例对象的方法调用
SharedInstanceEnum.INSTANCE.getNameString();
System.out.println(SharedInstanceEnum.INSTANCE.name());
System.out.println(SharedInstanceEnum.INSTANCE.valueOf("INSTANCE"));
System.out.println(SharedInstanceEnum.INSTANCE.val2);
demo.test();
}
}
//通过枚举解决单例模式
enum SharedInstanceEnum{
//这个就是单例的实例对象
INSTANCE;
//成员变量
private String val1;
Integer val2;
//赋值方法
public void config(String val1) {
this.val1 = val1;
}
//获取值方法
public String getNameString() {
return val1;
}
//业务代码方法
public void test(){
System.out.println(val1+val2);
}
}