单例模式
1. 饿汉、懒汉模式
通过特定技巧, 保证在一个进程中某个类只有一个实例对象
具体看代码理解
饿汉模式:
饿 -> 早 (急迫) -> 类加载的时候, 就初始化对象
查看代码
// 单例, 饿汉模式
// 唯一实例创建时机非常早. 类似于饿了很久的人, 看到吃的就赶紧开始吃. (急迫)
class Singleton {
// 类的静态成员, 在类加载的时候 (这里简单理解为, jvm一启动就加载) 初始化
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
// private修饰 构造方法
// 确保类外不能创建对象, 这样就保证了只有一个实例对象
private Singleton() {
}
}
class Test {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
懒汉模式:
懒 -> 缓 -> 用到时候再实例化对象
// 单例模式, 懒汉模式的实现
class SingletonLazy {
private static SingletonLazy instance = null;
public static SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
private SingletonLazy() {}
}
class Demo28 {
public static void main(String[] args) {
SingletonLazy s1 = SingletonLazy.getInstance();
SingletonLazy s2 = SingletonLazy.getInstance();
System.out.println(s1 == s2);
// new SingletonLazy();
}
}
2. 饿汉模式、懒汉模式 是否线程安全 ?
饿汉模式线程安全, 懒汉模式线程不安全
静态变量instance实例创建, 在main线程执行之前, 在其他所有线程启动之前
所以, 当多个线程调用getInstance() 方法, 相当于只是读取instance变量的值 -> 安全
假设下面的执行顺序, 实例化了两次对象 -> 线程不安全
如何解决 -> 加锁
查看代码
// 单例模式, 懒汉模式的实现
class SingletonLazy {
private static SingletonLazy instance = null;
private static Object locker = new Object();
public static SingletonLazy getInstance() {
synchronized (locker) {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
private SingletonLazy() {}
}
class Demo28 {
public static void main(String[] args) {
SingletonLazy s1 = SingletonLazy.getInstance();
SingletonLazy s2 = SingletonLazy.getInstance();
System.out.println(s1 == s2);
// new SingletonLazy();
}
}
现在虽然把线程安全问题解决了, 但是仔细观察下面代码发现
public static SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
只有最初调用getInstance()
,存在线程安全问题, 创建实例对象之后调用就只是读取instance变量的操作, 不存在线程安全问题
创建实例对象之后, 调用getInstance, 明明没有线程安全问题了, 但是还是要跑一趟 加/解锁操作 -> 不好
因为, 一般来说, 某个代码里面有锁, 就基本上谈不上高性能了
public static SingletonLazy getInstance() {
synchronized (locker) {
if (instance == null) {
instance = new SingletonLazy();
}
}
return instance;
}
解决 -> 在外面套个判断
查看代码
// 单例模式, 懒汉模式的实现
class SingletonLazy {
private static SingletonLazy instance = null;
private static Object locker = new Object();
public static SingletonLazy getInstance() {
// 双重if
if (instance == null) {
synchronized (locker) {
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
private SingletonLazy() {}
}
class Demo28 {
public static void main(String[] args) {
SingletonLazy s1 = SingletonLazy.getInstance();
SingletonLazy s2 = SingletonLazy.getInstance();
System.out.println(s1 == s2);
// new SingletonLazy();
}
}
另外, 得针对 instance 静态变量 + volatile
1. 避免编译器优化 ( 内存可见性 ) -> 防止读内存优化到读寄存器
2. 避免编译器优化 ( 指令重排序问题 )
什么是指令重排序问题 ?
编译器会在逻辑等价的情况下, 改变(由代码编译而成)二进制指令的顺序, 为了提高代码的执行效率
看下面这个例子:
instance = new SingletonLazy(), 会大致编译成3个指令
1. 申请内存空间
2. 调用构造方法
3. 把对象的地址赋值给变量
在单线程代码中, 即使2、3执行顺序改变, 也不会有影响 -> 申请内存空间 -> 把对象的地址赋值给变量 -> 调用构造方法
但是, 在多线程中会有问题, 看下面例子
t2线程中拿到的 instance 变量, 还没有进行初始化成员变量, 针对为初始化的成员进行操作, 会有问题
标签:getInstance,SingletonLazy,instance,static,new,线程,设计模式 From: https://www.cnblogs.com/xumu7/p/18139854