目录
单例模式介绍
-
说明:保证一个类只有一个实例,并提供一个全局访问点
- 场景:数据库连接池的设计、
- 优点:在内存中只有一个实例,减少内存开销、全局统一的访问点可以严格控制对象的访问
- 缺点:没有接口,扩展困难
- UML图:客户端访问单例类,单例类负责控制任何情况下只有一个实例
单例模式是工作中高频使用的设计模式之一。单例模式可以确保内存中单例类只有一个实例,有效的减少了内存的开销,避免了类的重复创建和销毁。
懒汉式为什么是线程不安全的
单例模式有两种实现方式,即大家所熟悉的饿汉式和懒汉式。二者的区别是创建实例的时机,饿汉式在应用启动时就创建了 实例,饿汉式是线程安全的,是绝对单例的。懒汉式在对外提供的获取方法被调用时会实例化对象。在多线程情况下,懒汉模式不是线程安全的,示范代码如下:
public class LazySingleTon {
private static LazySingleTon lazySingleTon = null;
private LazySingleTon(){
}
public static LazySingleTon getInstance(){
if(lazySingleTon == null){
//对下面的代码加断点
lazySingleTon = new LazySingleTon();
}
System.out.println(lazySingleTon);
return lazySingleTon;
}
public static void main(String[] args) {
for(int i = 1 ; i <= 2 ;i ++ ){
Thread thread = new ForTestThread();
thread.start();
}
}
}
public class ForTestThread extends Thread {
@Override
public void run() {
LazySingleTon lazySingleTon = LazySingleTon.getInstance();
}
}
操作如下
配合idea的多线程debug,执行main方法,在实例化处加断点。当第一个线程执行验证了lazySingleTon对象为null,准备new一个新对象时第二个线程也验证完了lazySingleTon为null,这时两个线程都会实例化LazySingleTon,执行完成输出的内容:
Connected to the target VM, address: '127.0.0.1:49751', transport: 'socket'
com.rbac.console.singleton.LazySingleTon@492dccc0
Disconnected from the target VM, address: '127.0.0.1:49751', transport: 'socket'
com.rbac.console.singleton.LazySingleTon@2f17749c
打印内容显示同一个内存中存在两个实例分别是:LazySingleTon@2f17749c、LazySingleTon@492dccc0。这表明在多线程情况下有一定几率会出现一个单例类会有多个实例,证明懒汉式是非线程安全的。
懒汉模式实现线程安全
1.对实例化方法加锁
public static synchronized LazySingleTon getInstance(){
if(lazySingleTon == null){
lazySingleTon = new LazySingleTon();
}
System.out.println(lazySingleTon);
return lazySingleTon;
}
加锁可以确保实例话方法只能有一个线程执行,确保两个线程不会同时进入实例化方法。缺点是同步锁的加锁和解锁比较消耗资源,而且synched关键字修饰static方法时锁的是整个class,对性能会有影响
2.double check+volatile方案
public static LazySingleTon getInstance(){
if(lazySingleTon == null){
synchronized (LazySingleTon.class) {
if (lazySingleTon == null) {
//1.分配内存
//2.初始化对象
//3.设置LazySingleTon类执行内存
lazySingleTon = new LazySingleTon();
}
}
}
System.out.println(lazySingleTon);
return lazySingleTon;
}
double check方案可以实现线程安全,且降低内存消耗。代码中三行注释代表了new对象时底层进行了三步操作,由于JVM优化算法可能会指令重排,也就是第二步和第三步执行的顺序会互换。这样可能会出现第一个第一个线程出现了指令重排情况,还没来得及初始化对象,第二个线程就进行了对象获取,导致获取到的对象是null。
为避免这种情况的发生可以使用volatile关键字修饰静态类,禁止指令重排
private volatile static LazySingleTon lazySingleTon = null;
也可以使用静态内部类的初始化延迟加载解决,静态内部类有初始化锁,达到线程安全的目的
public class LazySingleTon {
private LazySingleTon(){
}
public static LazySingleTon getInstance(){
return InnerClass.lazySingleTon;
}
private static class InnerClass{
private static LazySingleTon lazySingleTon = new LazySingleTon();
}
public static void main(String[] args) {
for(int i = 1 ; i <= 2 ;i ++ ){
Thread thread = new ForTestThread();
thread.start();
}
}
}
文章内容参考自慕课网
设计模式学习友情链接:
这位小可爱,如果觉得文章不错,请关注或点赞 (-__-)谢谢