设计模式之单例模式

创建型模式之单例模式(Singleton)

什么是单例模式

  • 该类只有一个实例
  • 构造方法是私有的
  • 有一个获取该类对象的静态方法getInstance()

应用场景

  • 一个国家只有一个主席
  • 如果此时的限定必须是抽象出来的类只能是一个对象,这个时候就需要使用单例模式

懒汉式

什么是懒汉式

  • 懒汉式是当用到这个对象的时候才会创建,即是在getInstance()方法创建这个单例对象

优缺点

  • 只有用到的时候才会创建这个对象,因此节省资源
  • 线程不安全
    • 我们知道一旦我们使用了懒汉式就是在getInstance()方法中创建这个单例对象,那么不可避免的就是线程安全问题

实现

/**
 * 懒汉式的单例模式: 不是线程安全的
 * 优点: 在使用的时候才会初始化,可以节省资源
 */
public class SignalLazy {
	// 将默认的构造器设置为private类型的
	private SignalLazy() {
	}

	// 静态的单例对象
	private static SignalLazy instance;

	//静态的获取单例的对象,其中有一个判断,如果没有初始化,那么就创建
	public  static SignalLazy getInstance() {
		// 如果instance没有被初始化,那么就创建即可,这个是保证了单例,但是并不是线程安全的
		if (instance == null) {
			System.out.println("this is SignalLazy");
			instance = new SignalLazy(); // 创建一 个对象
		}
		return instance; // 返回这个对象
	}
}
  • 从上面的代码中我们可以知道一旦使用多线程创建对象,那么就会出现线程不安全,最后创建出来的就不是单例了
  • 测试代码如下
public class MainTest {

	public static void main(String[] args) {
		 new Thread(new Runnable() {

			@Override
			public void run() {
				//创建实例,并且输出其中的地址,如果地址相同, 那么就是同一个实例
				System.out.println("this is"+ SignalLazy.getInstance());

			}
		}).start();

		 //主线程也是创建输出其中的地址,运行可以看出这两个地址是不一样的
		 System.out.println("this is"+SignalLazy.getInstance());

	}

}

解决线程不安全

  • 线程同步锁(synchronized)
    • 我们知道每一个类都有一个把锁,我们可以使用线程同步锁来实现线程同步方法
    • 但是使用线程同步锁浪费资源,因为每次创建实例都需要请求同步锁,浪费资源
public synchronized static SignalLazy getInstance() {
		// 如果instance没有被初始化,那么就创建即可,这个是保证了单例,但是并不是线程安全的
		if (instance == null) {
			System.out.println("this is SignalLazy");
			instance = new SignalLazy(); // 创建一个对象
		}
		return instance; // 返回这个对象
	}
  • 双重校验
    • 双重校验: 两次判断单例对象是否为 null,这样的话,当当线程经过这个判断的时候就会先判断,而不是等待,一旦判断不成立,那么就会继续执行,不需要等待
    • 相对于前面的同步方法更加节省资源
public class SignalTonDoubleCheck {
	private volatile static SignalTonDoubleCheck instance = null;

	private SignalTonDoubleCheck() {
	}; // 将默认的构造方法设置私有

	public static SignalTonDoubleCheck getInstance() {
		if (instance == null) {
			synchronized (SignalTonDoubleCheck.class) {
				if (instance == null) {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					// 这个new 并不是原子操作,因此当多线程进行到这里需要及时刷新这个值,因此要设置为voliate
					instance = new SignalTonDoubleCheck();
				}
			}
		}
		return instance;
	}

}
  • 匿名内部类 (推荐使用)
    • 我们知道静态变量、静态代码块、静态方法都是在类加载的时候只加载一次
public class SignalTonInnerHolder {
	//私有构造函数
	private SignalTonInnerHolder() {
	}

	/*
	 * 匿名内部类,其中利用了静态成员变量在类加载的时候初始化,并且只加载一次,因此保证了单例
	 */
	private static class InnerHolder {
		private static SignalTonInnerHolder instance = new SignalTonInnerHolder();
	}

	public static SignalTonInnerHolder getInstance() {
		return InnerHolder.instance;  //加载类
	}
}
  • 一旦加载SignalTonInnerHolder类的时候就会加载其中的静态类,随之加载的就是其中的创建对象语句,因此在类加载的时候就完成了创建,这个和我们后面说的饿汉式有点相同

饿汉式

什么是饿汉式

  • 在类加载的时候就创建单例对象,而不是在getInstance()方法创建
  • 所谓的饿汉式就是利用静态成员变量或者静态语句块在类加载的时候初始化,并且只初始化一次,因此这个是线程安全的,但是在没有用到的时候就初始化,那么是浪费资源

优缺点

  • 还没用到就创建,浪费资源
  • 类加载的时候就创建,线程安全

实现

/*
 * 饿汉式:线程安全
 *
 */
public class SignalHungry {
	private SignalHungry() {
	}

	// 静态变量只有在类加载的时候初始化一次,因此这个是线程安全的
	private static SignalHungry instance = new SignalHungry();

	public static SignalHungry getInstance() {
		return instance;
	}

}

测试

public class MainTest {

	public static void main(String[] args) {
		 new Thread(new Runnable() {

			@Override
			public void run() {
				//创建实例,并且输出其中的地址,如果地址相同, 那么就是同一个实例
				System.out.println("this is"+ SignalHungry.getInstance());

			}
		}).start();

		 //主线程也是创建输出其中的地址,运行可以看出这两个地址是不一样的
		 System.out.println("this is"+SignalHungry.getInstance());

	}

}

总结

  • 饿汉式在类加载的时候就会创建单例对象,因此浪费资源
  • 懒汉式在用到的时候才创建,节省资源,但是线程不安全,但是我们可以使用匿名内部类的方式使其线程安全
  • 一般在使用的时候会使用懒汉式的匿名内部类的实现和饿汉式的创建方式

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Linux驱动

22.QT-QXmlStreamReader解析,QXmlStreamWriter写入

XML 用于存储数据,数据的形式类似于树结构(参考: http://www.runoob.com/xml/)

1435
来自专栏ml

strcpy和memcpy的区别

strcpy和memcpy都是标准C库函数,它们有下面的特点。 strcpy提供了字符串的复制。即strcpy只用于字符串复制,并且它不仅复制字符串内容之外,还...

3346
来自专栏小二的折腾日记

day5(面向对象2)

用来将文件或文件夹封装成对象。 方便对文件与文件夹的属性信息进行操作。 File对象可以作为参数传递给

651
来自专栏小筱月

单例模式 实现

上面那种直接在方法上加锁的方式其实不够好,因为在方法上加了内置锁在多线程环境下性能会比较低下,所以我们可以将锁的范围缩小

3192
来自专栏行者常至

java.lang.ClassCastException: java.lang.String cannot be cast to com.qbz.entity.TblUser

863
来自专栏IT可乐

深入理解计算机系统(3.6)------汇编的流程控制

  前面我们所讲的所有指令,代码执行顺序都是一条接着一条顺序的执行。但是实际上在编码过程中,会有某些结构,比如条件语句(if-else),循环语句(for,do...

2157
来自专栏JetpropelledSnake

SQL学习笔记之简易ORM

1 、我在实例化一个user对象的时候,可以user=User(name='lqz',password='123')

531
来自专栏决胜机器学习

Shell基本操作与命令

Shell基本操作与命令 (原创内容,转载请注明来源,谢谢) 本文主要是我最近学习shell语言的学习笔记,主要在于通过学习这些内容,达到看得懂shell脚...

3505
来自专栏IT可乐

Java IO详解(一)------File 类

File 类:文件和目录路径名的抽象表示。 注意:File 类只能操作文件的属性,文件的内容是不能操作的。 1、File 类的字段 ?   我们知道,各个平台之...

2179
来自专栏python爬虫日记

转载:python的编码处理(一)

最近业务中需要用 Python 写一些脚本。尽管脚本的交互只是命令行 + 日志输出,但是为了让界面友好些,我还是决定用中文输出日志信息。 

1012

扫码关注云+社区

领取腾讯云代金券