单例模式
定义
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
特点
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
为什么需要单例模式
1.资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如日志文件,应用配置。
2.控制资源的情况下,方便资源之间的互相通信。如线程池等。
单例的实现方式
饿汉式
是否Lazy初始化:否
是否多线程安全:是
描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
public class HungrySingleton {
private static final HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return instance;
}
}
懒汉式
是否 Lazy 初始化:是
是否多线程安全:是
描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。
public class LazySingleton {
//保证instance在所有线程中同步
private static volatile LazySingleton instance = null;
private LazySingleton(){}
public static synchronized LazySingleton getInstance(){
if (instance==null){
instance = new LazySingleton();
}
return instance;
}
}
后面再讨论volatile
关键字
双检锁
是否 Lazy 初始化:是
是否多线程安全:是
描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
getInstance() 的性能对应用程序很关键。
public class DoubleCheck {
private static volatile DoubleCheck instance = null;
private DoubleCheck(){}
public static DoubleCheck getInstance(){
if (instance==null){
synchronized (DoubleCheck.class){
if (instance==null){
instance = new DoubleCheck();
}
}
}
return instance;
}
}
静态内部类
是否 Lazy 初始化:是
是否多线程安全:是
描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
public class StaticInnerClass {
private static class SingletonHolder{
private static final StaticInnerClass INSTANTS = new StaticInnerClass();
}
private StaticInnerClass(){}
public static final StaticInnerClass getInstance(){
return SingletonHolder.INSTANTS;
}
}
枚举
是否 Lazy 初始化:否
是否多线程安全:是
描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。
public enum Enumerate {
INSTANCE;
}
volatile关键字
Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。
被volatile修饰的变量能够保证每个线程能够获取该变量的最新值,从而避免出现数据脏读的现象。
特性:
1.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(实现可见性)
2.禁止进行指令重排序。(实现有序性)
3.volatile 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性。
禁止指令重排序优化:有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。
volatile 性能:volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。
volatile和synchronized两者之间比较:
- 关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好;
- volatile只能修饰变量,而synchronized可以修饰方法、代码块等。
- 多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。
- volatile能保证数据的可见性,但不能保证数据的原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步处理。
- 关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性。
转载:https://blog.csdn.net/weixin_46399870/article/details/106864048