目录
了解什么是单例模式
单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)
什么时候使用单例模式,有什么优缺点
1.类 加载到内存后,需实例化一个单例,JVM保证线程安全
2.当某些情况下,我们只需要new一个对象
扫描二维码关注公众号,回复:
15943636 查看本文章
3.spring接管对象的时候默认就是单例的
4.优点:简单实用
5.缺点:不管是否用到,类加载的时候都完成实例化
单例的两种基本模式
饿汉式单例——
/**
* @description: 最简单的单例模式----饿汉式
* @author: jd
* @date: 2022年2月27日
*/
public class Mgr01 {
//一开始自己new一个 但是是私有方法 只能自己new
private static final Mgr01 INSTANCE = new Mgr01();
//构造函数
private Mgr01(){};
//如果别人想用的话 提供一个方法
public static Mgr01 getInstance(){
return INSTANCE;
}
public void m(){System.out.println("m");}
//作为客户端 不管调用多少次 都是同一个
public static void main(String[] args) {
Mgr01 m1 = Mgr01.getInstance();
Mgr01 m2 = Mgr01.getInstance();
System.out.println(m1==m2);
}
}
问题一:
此模式为饿汉模式 不管我用不用这个对象都会提前new一个出来 占用内存
解决办法: 懒汉式单例
懒汉式单例——
/**
* 达到了按需初始化的目的 但是带来了线程不安全
*/
public class Mgr02 {
private static Mgr02 INSTANCE;
private Mgr02(){
}
public static Mgr02 getInstance(){
// 先判断 INSTANCE是否为空 如果为空再去new
if (INSTANCE == null){
try {
Thread.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
INSTANCE = new Mgr02();
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> System.out.println(Mgr02.getInstance().hashCode())).start();
}
}
}
问题二:当多线程的环境下 不能保证对象只有一个
假设当第一个线程进行判断的时候 判断为空 还没开始newINSTANCE 这时候 暂停切换第二个线程 第二个线程判断也为空 然后new了一个 INSTANCE 然后切换回第一个线程 接着又new了一个 INSTANCE 当线程无限多的时候 就有可能会new很多出来 造成线程不安全 程序就不是单例了
解决:加锁
进阶——给单例加锁
加锁式单例——
/**
* @description: 加锁式单例 synchronized
* @author: jd
* @date: 2022年2月27日
*/
public class Mgr03 {
private static Mgr03 INSTANCE;
private Mgr03(){
}
public static synchronized Mgr03 getInstance(){
/**
* 假设这里是业务逻辑
*/
if (INSTANCE == null){
try {
Thread.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
INSTANCE = new Mgr03();
}
return INSTANCE;
}
public void m () {System.out.println("m");}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> System.out.println(Mgr02.getInstance().hashCode())).start();
}
}
}
// synchronized 上锁
/*
* 过程:1. 首先有一把锁 当第一个线程抢到锁以后把锁锁上 完成逻辑后 在把锁打开 其他线程才能进入
*/
问题三:当我们的方法包含了一些太复杂的业务逻辑的时候而且这些业务逻辑不涉及到读写 我们没有必要将这些业务逻辑也锁进去 这样称为锁的粒度太粗了
DCL(Double Check Lock)双重校验锁式单例——
/**
* @description: 双重锁+单例
* @author: jd
* @date: 2022年2月27日
*/
public class Mgr05 {
private static volatile Mgr05 INSTANCE;
private Mgr05(){
}
public static Mgr05 getInstance(){
/**
* 假设这里是业务逻辑
*/
if (INSTANCE == null){ //Double Check Lock
//双重检查
synchronized (Mgr05.class){
if (INSTANCE ==null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mgr05();
}
}
}
return INSTANCE;
}
public void m () {System.out.println("m");}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> System.out.println(Mgr02.getInstance().hashCode())).start();
}
}
}
这里为什么要有两重判断呢 直接加锁判断不就行了——
主要是为了性能--一旦进行判断后发送已经有了实例了 就减少了竞争锁的过程