单例模式

概念 & 原理

1. 定义

  • 保证一个类仅仅有一个实例,并提供一个全局访问点
  • 类型:创建型

2. 适用场景

  • 想确保任何情况下都绝对只有一个实例
  • 实际场景:数据库连接池

3. 优缺点

  • 优点:在内存里只有一个实例,减少了内存开销;可以避免对资源的多重占用;设置了全局访问点,严格控制访问(对外不让new)
  • 缺点:没有借口,扩展困难

4.单例模式的重点内容

  • 私有构造器
  • 线程安全
  • 延迟加载
  • 序列化和反序列化
  • 反射

懒汉式案例

public class Singleton {
	//懒汉式
	private static Singleton uniqueInstance;
	private Singleton(){}
	
	//保证线程安全
	public static  synchronized Singleton getInstance(){
		if(uniqueInstance==null)
		 uniqueInstance =  new Singleton();
		return uniqueInstance;
	}
}
public class Application {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Singleton instance1 = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		
		System.out.println(instance1.hashCode());
		System.out.println(instance2.hashCode());
	}

}

运行结果:
在这里插入图片描述

多线程debug验证线程安全

package com.rjxy.demo1;

public class Application {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		Thread t1 = new Thread(new T());
		Thread t2 = new Thread(new T());
		t1.start();
		t2.start();
		System.out.println("end...");
	}

}

class T implements Runnable{

	@Override
	public void run() {
		 Singleton singleton = Singleton.getInstance();
		 System.out.println(Thread.currentThread().getName()+" "+singleton.hashCode());
	}
	
}

开启debug模式:

  • 可以发现,三个线程在三个断点处都已经停了下来,suspend表示挂起
    在这里插入图片描述

1. 情况一调试

  • 让线程 0 先进入 if 语句,但不要让线程 0 为 uniqueInstance 赋值:
    在这里插入图片描述
  • 让线程 1 也进去,让其为 uniqueInstance 赋值:
    在这里插入图片描述
  • 回到线程 0 ,让其也为 uniqueInstance 赋值:
    在这里插入图片描述
  • 线程 1 还没有返回,在这种情况之下,uniqueInstance 的返回值是最后的那个创建实例的线程:
    在这里插入图片描述
    在这种情况下,线程生成了不止一个实例,但之所以输出相同,是概率原因。如果有很多线程的话,且生成对象需要消耗资源,那么很可能导致系统故障

2. 情况二调试

  • 在情况一的最后,不再回到线程 0 为 uniqueInstance 赋值,而是先线程一直接返回输出。
    在这里插入图片描述
  • 线程 1 输出后,线程 0 创建对象输出
    在这里插入图片描述
  • 可以看到,单例模式竟然创建了两个对象!存在线程安全问题

3. 解决方法

  1. synchronized关键字。需要注意的是如果加在静态方法上锁的是这个类的class文件。如果不是静态方法,相当于锁的堆内存中生成的对象。
//保证线程安全
	public static synchronized Singleton getInstance(){
		if(uniqueInstance==null)
		 uniqueInstance =  new Singleton();
		return uniqueInstance;
	}

在这里插入图片描述
缺点:synchronized 存在加锁和解锁的开销。而且锁的是class文件,范围比较大,对系统性能有一定影响。

  1. DoubleCheck双重检查实战原理
    下面这种方式将加锁推迟到if语句之后,带来的好处就是在实例被创建出来之后,获取该实例的时候,就不必大费周章地去加锁解锁,节约资源。但是如果是多线程的话,就有大问题了。 uniqueInstance = new Singleton();这句代码要经过:①分配内存空间、②初始化对象、③将索引指向该内存空间。其中②和③在多线程的情况下会发生重排序,造成异常的出现,当然这是有概率的,并不绝对。解决办法就是 volatile 关键字,这个关键字会避免重排序的发送。关于重排序、volatile原理我会在后面的博客中讲到。
public class Singleton {
	//懒汉式
	private volatile static Singleton uniqueInstance;
	private Singleton(){}
	
	//保证线程安全
	public static synchronized Singleton getInstance(){
		if(uniqueInstance==null) {
			synchronized(Singleton.class) {
				 uniqueInstance =  new Singleton();
			}
		}
		
		return uniqueInstance;
	}
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值