线程安全的单例模式设计
一、单例模式
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点。单例模式的特点:
唯一实例:保证在整个应用程序中只存在一个类的实例
全局访问:提供统一的访问入口(通常通过静态方法)
1.1 饿汉式
饿汉式单例模式是一种在类加载时就创建实例的单例实现方式,通过静态变量保存唯一实例。其核心特点是急切初始化,线程安全但可能造成资源浪费。
主要实现要点:
私有静态变量:类加载时立即初始化实例
私有构造函数:防止外部实例化
静态访问方法:提供全局访问入口
// 使用静态变量记录唯一实例【以空间换时间】
private static Singleton singleton = new Singleton();
private Singleton (){}
public static Singleton getInstance (){
return singleton ;
}
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton1.equals(singleton2));
输出为:true
1.2 懒汉式
使用synchronized方法锁实现单例模式主要有两种典型方式:懒汉式同步方法和双重检查锁定模式。案例1,使用同步方法实现。
线程安全:通过synchronized修饰静态方法,确保多线程环境下只会创建一个实例
性能缺陷:每次调用getInstance()都需要获取锁,即使实例已存在,造成不必要的性能开销
1、同步方法
// 使用静态变量记录唯一实例
private static Singleton singleton = null;
// 构造方法私有化,防止外部创建实例
private Singleton (){}
// 控制线程安全,但是性能不好
public static synchronized Singleton getInstance (){
if (singleton == null){
singleton = new Singleton() ;
}
return singleton ;
}
2、双重检查锁(double-check)
// 使用静态变量记录唯一实例
// volatile可以确保当singleton被初始化后,多线程才可以正确处理
// 被volatile修饰的变量的值,将不会被本地线程缓存
// 对该变量读写都是直接操作共享内存,确保多个线程能正确的处理该变量。
private static volatile Singleton singleton = null ;
// 构造方法私有化,防止外部创建实例
private Singleton (){}
public static Singleton getInstance (){
// 如果实例不存在,则进入同步区
if (singleton == null){
// 只有第一次才会彻底执行这里面的代码
synchronized (Singleton.class) {
if (singleton == null){
singleton = new Singleton() ;
}
}
}
return singleton ;
}
分析:当singleton没有被volatile修饰,多个线程同时执行if(singleton == null)时,由于以下方法为synchronized修饰,因此线程阻塞,singleton为空,去创建实例后方法执行结束。后续线程进入同步方法块,由于singleton会被本地线程缓存,因此数据不会实时被其他线程获取,因此存在多个实例创建的情况。
1.3 并发编程概念
1、原子性
一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。eg.银行转账
2、可见性
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
3、有序性
程序执行的顺序按照代码的先后顺序执行。
4、指令重排序
处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
1.4 volatile
1、可见性
1、使用volatile关键字会强制将修改的值立即写入主存;
2、使用volatile关键字后,其他线程修改主线程的值,会将主线程数据缓存设置为无效
3、主线程的缓存值无效,会从主存读取最新的数值。
2、有序性
volatile关键字能禁止指令重排序
1、当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;
2、在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
3、原子性
volatile无法保证原子性
二、使用场景
Java中的Runtime类是单例模式的经典实现,采用饿汉式实现方式确保全局唯一实例。
静态实例初始化
构造方法私有化
通过private Runtime(){}禁止外部实例化,确保控制权在类内部
全局访问点
提供public static Runtime getRuntime()静态方法作为唯一访问入口,返回预创建的静态实例
2.1 jdk
1、静态实例初始化
在类加载阶段通过private static Runtime currentRuntime = new Runtime()直接初始化实例,利用JVM类加载机制保证线程安全。
2、构造方法私有化
通过private Runtime(){}禁止外部实例化,确保控制权在类内部。
3、全局访问点
提供public static Runtime getRuntime()静态方法作为唯一访问入口,返回预创建的静态实例。
Runtime runtime1 = Runtime.getRuntime() ;
Runtime runtime2 = Runtime.getRuntime() ;
/*
* 1229416514
* 1229416514
*/
System.out.println(runtime1.hashCode());
System.out.println(runtime2.hashCode());
2.2 jdbc
/** 懒汉式 **/
private static volatile Connection connection = null;
private JdbcConnectionDoubleCheckLazyModeUtils(){
}
/**
* 加载驱动
* */
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取数据库连接,double check双重校验
* */
public static Connection getConnection() {
if(null == connection){
synchronized(JdbcConnectionDoubleCheckLazyModeUtils.class) {
if(null == connection){
try{
connection = DriverManager.getConnection(JdbcCst.JDBC_URL, JdbcCst.USER_NAME, JdbcCst.USER_PASSWORD);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return connection;
}