设计模式详解(一)单例模式

1. 简介

顾名思义,单例即单一的实例,确切地讲就是指在某个系统中只存在一个实例,同时提供集中、统一的访问接口,以使系统行为保持协调一致。singleton一词在逻辑学中指“有且仅有一个元素的集合”,这非常恰当地概括了单例的概念,也就是“一个类仅有一个实例”


2. 饿汉模式

初始阶段就主动进行实例化,并时刻保持一种渴求的状态,无论此单例是否有人使用

饿汉模式没有线程安全性问题

class Sun{
	private static final Sun sun=new Sun();
	private Sun() {//不能省略  默认的Sun有一个public的构造函数
		
	}
	public static Sun getInstance() {
		return sun;
	}
}
public class SingletonDemo {
	public static void main(String[] args) {
		Sun sun1=Sun.getInstance();
		Sun sun2=Sun.getInstance();
		System.out.println(sun1==sun2);//true
	}
}
  1. Sun类的构造函数重写成私有的,保证其他的类不能通过构造函数来创建Sun的对象
  2. static关键字保证单例对象与类同在,在类加载的时候就初始化了
  3. final关键字保证Sun对象是一个常量

3. 懒汉模式

初始阶段就不进行实例化,当有人使用时才初始化

1. 懒汉模式版本1

class Sun{
	private static Sun sun;
	private Sun() {
		
	}
	public static Sun getInstance() {
		if(sun==null)//1
			sun=new Sun();
		return sun;
	}
}
public class SingletonDemo {
	public static void main(String[] args) throws InterruptedException {
		
		for(int i=0;i<5;i++) {
			new Thread(()->{
				System.out.println(Thread.currentThread().getName()+"  "+Sun.getInstance());
			},"thread"+i).start();;
		}
		Thread.sleep(3000);
	}
}

  1. 上面的代码在外部调用时才创建Sun对象并返回
  2. 但是代码中存在问题:在多线程情况下,如果有多个线程同时达到代码1处,那么就会创建多个实例对象,违背了单一原则,比如上面的main方法执行的结果如下:
    在这里插入图片描述

从运行结果就可以看出,5个线程获得的对象并不完全一样,因此上述版本存在线程安全问题

2. 懒汉模式版本2

为了解决线程安全问题,我们可以枷锁,代码如下:

package designpattern;

class Sun{
	private static Sun sun;
	private Sun() {
		
	}
	public static synchronized Sun getInstance() {
		if(sun==null)
			sun=new Sun();
		return sun;
	}
}
public class SingletonDemo {
	public static void main(String[] args) throws InterruptedException {
		
		
		for(int i=0;i<5;i++) {
			new Thread(()->{
				System.out.println(Thread.currentThread().getName()+"  "+Sun.getInstance());
			},"thread"+i).start();;
		}
		Thread.sleep(3000);
		
		
		
	}
}



在这里插入图片描述

从运行结果来看,现在对象唯一了,但是上面的代码效率不高,线程只要进入方法就进行加锁,可能有的线程来的比较晚,来的时候对象已经创建了,对于这类线程其实是不需要进行枷锁的

3. 懒汉模式版本3

package designpattern;

class Sun{
	private static Sun sun;
	private Sun() {
		
	}
	public static Sun getInstance() {
		if(sun==null) {
			synchronized (Sun.class) {
				if(sun==null) {
					sun=new Sun();
				}
			}
		}
		
		return sun;
	}
}
public class SingletonDemo {
	public static void main(String[] args) throws InterruptedException {
		
		
		for(int i=0;i<5;i++) {
			new Thread(()->{
				System.out.println(Thread.currentThread().getName()+"  "+Sun.getInstance());
			},"thread"+i).start();;
		}
		Thread.sleep(1000);
	}
}

在这里插入图片描述

		if(sun==null) {//Thread1 Thread2 Thread3 Thread4   代码1
		//Thread1 Thread2 Thread3 Thread4
			synchronized (Sun.class) {   //代码2
			//Thread1
				if(sun==null) {    //代码3
				//Thread1
					sun=new Sun();
				}

对这段代码的解释:

  1. 假设有4个线程同时到达了代码1处,这4个线程都可以进入第一个if块内
  2. 在代码2处由于是加锁,因此只有1个线程可以进入,假设线程1进入了同步代码块
  3. 线程1进入了同步代码块,进行代码3处的判断,对象为空,条件成立,因此线程1创建对象,执行结束,退出代码块
  4. 后面的线程2 3 4 也相继进入同步代码块中,但是由于第二个if条件不成立,因此不能创建对象
  5. 如果后面还有线程5 6 7等来到代码1处,对于这些后来的线程而言,第一个if条件就不成立,因此无法进入

这种方式称为懒加载模式的“双检锁

如果去掉第一个if: 和懒汉模式版本2的代码本质上一样,只要线程来了就要进行抢锁操作,耗费时间
如果去掉第二个if:当有多个线程通过了第一个if语句后,这些线程都会相继进入synchronized代码块中,没有第二个if就会创建多个实例对象

总的来说,第一个if保证线程效率,第二个if保证线程安全

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodePanda@GPF

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值