单例模式
让机器检查只能new一次对象
单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例.
饿汉模式(迫切)
类加载的同时, 创建实例.
// 单例模式(懒汉模式
class Singleton {
//创建一个静态成员,带有static就表示类属性
//每个类的类对象(编译后形成的.class文件,相当于文件图纸)是单例的
//类对象的属性(static),也就是单例的,也就是new出来的这个对象
//下面这个代码的执行时间,是在Singleton类被jvm加载的时候创建
private static Singleton instance = new Singleton();
//让构造方法变成private,这样就禁止了别人去new这个实例
private Singleton() {}
//获取唯一实例对象的方法
public static Singleton getInstance() {
return instance;//获取刚才创建的单例
}
}
public class Demo16 {
public static void main(String[] args) {
//这里s1和s2引用的对象是一样的
Singleton s1= Singleton.getInstance();
Singleton s2= Singleton.getInstance();
System.out.println(s1==s2);
}
}
//但是反射可以在当前单例模式中,创建出一个实例
//反射是属于"非常规"的编程手段,正常开发的时候,并应该使用或者慎重使用,滥用反射,会带来极大的风险,会使代码变得比较抽象,难以维护
懒汉模式-单线程版(延迟)
类加载的时候不创建实例. 第一次使用的时候才创建实例.
线程安全问题:
-
线程安全问题发生在首次创建实例时. 如果在多个线程中同时调用 getInstance 方法, (判断+new操作)不是原子操作(单个指令),就可能判断两个线程都没有new对象,就可能导致 创建出多个实例.
-
一旦实例已经创建好了, 后面再多线程环境调用 getInstance 就不再有线程安全问题了(不再修改 instance 了)
-
虽然new出来的会被覆盖,但是还是存在线程安全问题
-
加上 synchronized 可以改善这里的线程安全问题
无脑synchronized或者直接不要synchronized但是只能单线程
//懒汉模式(单线程)
class SingletonLazy {
//最开始为空
private static SingletonLazy instance = null;
//创建一个锁对象
private static Object locker = new Object();
//把构造方法设置成私有,避免他人new这个对象
private SingletonLazy() {
}
//首次运行getInstance才会创建对象
//线程不安全写法
public synchronized static SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();//new这个指令是原子的
}
return instance;
}
//线程安全但是效率低写法
// public synchronized static SingletonLazy getInstance() {
// // synchronized相当于就是把下面这一段指令打包,这样就变成原子性的了
// //也可以写成 synchronized (SingletonLazy.class) , 把这个类对象当锁对象(只要是用这个类创建的对象调用及锁)
// synchronized (locker){
// if (instance == null) {
// instance = new SingletonLazy();//new这个指令是原子的
// }
// return instance;
//
// }
// }
}
注意:
- 加锁是一个成本比较高的操作.加锁可能会引起阻塞等待
- 加锁的基本原则就是非必要不加锁,否则影响程序执行效率
- Vector HashTable StringBuffer 都是在关键方法上写了synchronized,无论是单线程还是多线程使用,无论是什么场景的线程安全问题,都是会加锁的
懒汉模式-多线程版
上面的懒汉模式的实现是线程不安全的,加上 synchronized 可以改善线程安全问题.但是后续每次getInstance就导致锁竞争和阻塞就导致了效率降低,这时候我们就可以用两个if来判断
双if判断是否要加锁
//懒汉模式(多线程)
class SingletonLazy {
//最开始为空
private static SingletonLazy instance = null;
//创建一个锁对象
private static Object locker =new Object();
//把构造方法设置成私有,避免他人new这个对象
private SingletonLazy() {}
//首次运行getInstance才会创建对象
public synchronized static SingletonLazy getInstance() {
// synchronized相当于就是把下面这一段指令打包,这样就变成原子性的了
//也可以写成 synchronized (SingletonLazy.class) , 把这个类对象当锁对象(只要是用这个类创建的对象调用及锁)
//第一个if是判断是否要加锁,减少了多次阻塞的问题.第二个if是判断是否要创建对象
if(instance == null){
synchronized (locker){
if (instance == null) {
instance = new SingletonLazy();//new这个指令是原子的
}
}
}
return instance;
}
}
public class Demo17 {
public static void main(String[] args) {
SingletonLazy s1=SingletonLazy.getInstance();
SingletonLazy s2=SingletonLazy.getInstance();
System.out.println(s1==s2);
}
}
小总结:
- 使用双重 if 判定, 降低锁竞争的频率.
懒汉模式-多线程版(改进)
单例模式中的内存可见性问题
- 问题所在:
第一个线程修改完instacnce之后,就结束了代码块,释放了锁,第二个线程就能够获得锁从阻塞中恢复,但是第二个线程对instacne的读取操作不一定就是instance修改后的结果,就可能导致读取的和修改的结果不同—内存可见性问题
- 解决方法:
给instance加上volatile,保证了该变量在RAM上实时更新为最新状态,任何时候读取都是最新的状态,而不会发生内存可见性问题
//懒汉模式(多线程)
class SingletonLazy {
//最开始为空
//加上volatile,只要数据更改就立马写回主存,不会留在寄存器而导致别的线程读取的不是更改后的结果
private static volatile SingletonLazy instance = null;
//创建一个锁对象
private static Object locker =new Object();
//把构造方法设置成私有,避免他人new这个对象
private SingletonLazy() {}
//首次运行getInstance才会创建对象
public synchronized static SingletonLazy getInstance() {
if(instance == null){
synchronized (locker){
if (instance == null) {//不能保证第二个线程读这个instacne的时候是第一个线程更改后的结果
instance = new SingletonLazy();
}
}
}
return instance;
}
}
- 对volatile的总结:
可以把volatile理解成,一个保证某个对象每次被读取的时候都是这个对象被修改并且存储到主存上的最终版本,每次访问都是没有再对这个对象做修改的时候且为最终版本的对象内容