#yyds干货盘点#设计模式之单例模式
Posted 汤圆学Java
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了#yyds干货盘点#设计模式之单例模式相关的知识,希望对你有一定的参考价值。
作者:汤圆
个人博客:javalover.cc
前言
有时候我们的类并不需要很多个实例,在程序运行期间,可能只需要一个实例就够了,多了反而会出现数据不一致的问题;
这时候我们就可以用单例模式来实现,然后程序中所有的操作都基于这个实例;
目录
单例模式有很多种,这里我们先列举下:
- 饿汉模式
- 懒汉模式-线程不安全
- 懒汉模式-线程安全
- 懒汉模式-线程不是很安全
- 懒汉模式-双重检查
- 静态内部类
- 枚举
正文
1. 饿汉模式(不推荐)
饿汉模式的核心就是第一次加载类的时候,进行数据的初始化;
而且这个数据不可被修改(final);
后续只能读,不能写。
这样一来,就保证了数据的准确性;
下面我们看下示例
package pattern.singleton;
// 饿汉模式(不推荐),因为占内存
public class HungryDemo {
private static final HungryDemo hungryDemo = new HungryDemo();
private HungryDemo() {
}
public static HungryDemo getInstance(){
return hungryDemo;
}
public static void main(String[] args) {
HungryDemo hungryDemo1 = HungryDemo.getInstance();
HungryDemo hungryDemo2 = HungryDemo.getInstance();
System.out.println(hungryDemo1);
System.out.println(hungryDemo2);
System.out.println(hungryDemo1 == hungryDemo2);
}
}
从主程序中可以看到,不管获取多少次实例,都是同一个。
2. 懒汉模式-线程不安全(不推荐)
懒汉模式,就是类初始化时不加载数据,等到需要的时候才加载;
下面看示例:
package pattern.singleton;
// 懒汉模式-线程不安全(不推荐)
public class LazyDemo1 {
private static LazyDemo1 lazyDemo;
private LazyDemo1(){
}
public static LazyDemo1 getInstance(){
if(lazyDemo == null)
lazyDemo = new LazyDemo1();
return lazyDemo;
}
public static void main(String[] args) {
LazyDemo1 l1 = LazyDemo1.getInstance();
LazyDemo1 l2 = LazyDemo1.getInstance();
System.out.println(l1);
System.out.println(l2);
System.out.println(l1 == l2);
}
}
这样做的好处就是节省资源,只在需要的时候才加载;
但是会导致一个问题,就是线程的安全性;
比如两个线程同时获取,有可能获取到不同的实例;
3. 懒汉模式-线程安全(不推荐)
上面的懒汉模式,最大的缺点就是线程不安全;
所以我们可以升级一下,通过加锁来解决,如下所示
package pattern.singleton;
// 懒汉模式-线程安全(不推荐)
public class LazyDemo2 {
private static LazyDemo2 lazyDemo;
private LazyDemo2(){
}
// 给方法加锁,线程安全了,但是效率低
public static synchronized LazyDemo2 getInstance(){
if(lazyDemo == null)
lazyDemo = new LazyDemo2();
return lazyDemo;
}
public static void main(String[] args) {
LazyDemo2 l1 = LazyDemo2.getInstance();
LazyDemo2 l2 = LazyDemo2.getInstance();
System.out.println(l1);
System.out.println(l2);
System.out.println(l1 == l2);
}
}
这样一来,不管多少个线程去获取实例,都只会获取到同一个;
但是缺点也很明显,就是效率低;
比如现在已经遗弃的vector类,就是通过给方法上锁,来解决安全问题
4. 懒汉模式-线程不是很安全(不推荐)
这一次,我们又升级了上面的懒汉模式,把方法锁改为代码块锁,减小了锁的范围;
package pattern.singleton;
import java.lang.management.ThreadInfo;
// 懒汉模式-线程不安全(不推荐)
// 解释:虽然加了代码同步块,但是还是存在线程不安全的情况
public class LazyDemo3 {
private static LazyDemo3 lazyDemo;
private static int count = 0;
private LazyDemo3(){
}
public static LazyDemo3 getInstance(){
if(lazyDemo == null){
// 1. 所有的线程会先执行下面的打印,然后第一个线程先获得锁,其他线程依次排队等待解锁
System.out.println(Thread.currentThread().getName());
synchronized (LazyDemo3.class){
try {
System.out.println(Thread.currentThread().getName()+"等待中");
// 2. 当第一个进来的线程在这里休眠时,其他外面的线程是获取不到锁的,就会一直等待
Thread.sleep(1000);
lazyDemo = new LazyDemo3();
System.out.println(Thread.currentThread().getName()+"等待结束");
// 3. 此时第一个线程释放锁,第二个线程因为已经通过了if(lazyDemo == null)的判断
// 所以会直接获取锁,然后重复刚才的步骤2,这样就会导致实例 lazyDemo 被创建多次
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
return lazyDemo;
}
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
@Override
public void run() {
LazyDemo3 l1 = LazyDemo3.getInstance();
System.out.println(l1);
}
}).start();
}
}
}
/**
* 下面是输出
*
* Thread-0
* Thread-0等待中
* Thread-1 // 此时Thread-1已经通过了if()校验
* Thread-0等待结束 // Thread-0 释放锁
* Thread-1等待中 // Thread-1 获取锁
* pattern.singleton.LazyDemo3@568acee4 // 这是 Thread-0 创建的单例
* Thread-1等待结束 // Thread-1 释放锁
* pattern.singleton.LazyDemo3@3f580216 // 这是 Thread-1 创建的单例,此时就有了两个单例,就出问题了
*
*
*/
通过例子可以看到,这两个线程交替执行去获取实例,虽然效率有所提高,但是结果却创建了两个实例,因小失大
所以这种方式也不推荐
5. 懒汉模式-双重检查(推荐)
前面的几种懒汉模式,都是各有各的不足;
所以这里来个大招,将上面的不足都解决掉;
也就是双重检查模式。
package pattern.singleton;
// 懒汉模式-双重检查(推荐)
public class LazyDemo4 {
// 保证可见性,即在多线程时,一个线程修改了这个变量,则其他线程立马就可以看到变化
private static volatile LazyDemo4 lazyDemo;
private LazyDemo4(){
}
public static LazyDemo4 getInstance(){
if(lazyDemo == null)
// 加同步代码块,保证当前只有一个线程在修改 lazyDemo
synchronized (LazyDemo4.class){
// 加双重检查,其他后面进来的线程,如果看到 lazyDemo 已经创建了,则不再创建,直接返回
if(lazyDemo == null)
lazyDemo = new LazyDemo4();
}
return lazyDemo;
}
public static void main(String[] args) {
LazyDemo4 l1 = LazyDemo4.getInstance();
LazyDemo4 l2 = LazyDemo4.getInstance();
System.out.println(l1);
System.out.println(l2);
System.out.println(l1 == l2);
}
}
可以看到,这里在获取到锁之后,又加了一个null判断,这样就可以保证在创建实例之前,确保实例真的是null
6. 静态内部类(推荐)
这个就比较简单了,不需要加锁,也不需要考虑null判断,直接将实例封装到内部类中,再用final修饰为不可变;
从而保证了这个实例的唯一性;
这个其实就是结合了前面的 饿汉模式 和 懒汉模式-双重检查。
package pattern.singleton;
// 静态内部类(推荐)
public class StaticInnerDemo {
private StaticInnerDemo(){
};
// 静态内部类
// 1. 当 StaticInnerDemo 加载时,下面的 InnerInstace 并没有加载
// 2. 当 调用getInstance()时,下面的静态内部类才会加载,且只会加载一次(因为final常量)
private static class InnerInstance{
private static final StaticInnerDemo staticInnerDemo = new StaticInnerDemo();
}
public static StaticInnerDemo getInstance(){
return InnerInstance.staticInnerDemo;
}
public static void main(String[] args) {
StaticInnerDemo staticInnerDemo1 = getInstance();
StaticInnerDemo staticInnerDemo2 = getInstance();
System.out.println(staticInnerDemo1);
System.out.println(staticInnerDemo2);
System.out.println(staticInnerDemo1 == staticInnerDemo2);
}
}
7. 枚举(推荐)
最后来个压轴的,通过枚举来实现单例模式;
这个可以说是极简主义风格,自带单例效果;
因为不需要过多的修饰,只是单纯的定义一个枚举,然后创建一个实例,后面程序直接用这个实例就可以了。
package pattern.singleton;
// 枚举(推荐)
public enum EnumDemo {
INSTANCE;
public static void main(String[] args) {
EnumDemo instance1 = EnumDemo.INSTANCE;
EnumDemo instance2 = EnumDemo.INSTANCE;
System.out.println(instance1);
System.out.println(instance2);
System.out.println(instance1 == instance2);
}
}
总结
关于单例模式的实现方式,首推的就是枚举,其次是懒汉模式-双重检查,最后是静态内部类
以上是关于#yyds干货盘点#设计模式之单例模式的主要内容,如果未能解决你的问题,请参考以下文章