Java中实现单例模式有很多种写法
主要介绍两种
饿汉模式和懒汉模式
举例
在计算机中的例子
比如打开一个文件,读取文件的内容并显示出来
饿汉: 把文件的所有内容打印出来并显示
懒汉: 只读取文件的一小部分内容,当用户翻页之后,再读取文件的其他内容,如果不翻页就省下了
假如文件特别大,饿汉文件打开可能需要半天,懒汉可以快速打开
代码举例饿汉模式
直接创建一个唯一的实例化对象
package threading;
class Singleton{
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
private Singleton(){
}
}
public class ThreadDemo10 {
Singleton singleton = new Singleton();
}
通过将构造方法私有化并且再Singleton方法里面创建唯一实例化对象
会发现无法在外部创建对象
'
打印出来发现是true, 1和2是同一个对象
在多线程下是否安全???
是安全的,多线程情况下只是进行读操作,并不会对这个对象进行修改,只是进行读数据,所以饿汉模式是线程安全的
代码举例创建懒汉模式
package threading;
class SingletonLazy{
private static SingletonLazy instance;
public static SingletonLazy getInstance(){
if(instance == null){
instance = new SingletonLazy();
}
return instance;
}
private SingletonLazy(){}
}
public class ThreadDemo11 {
}
这个在多线程下是否能保持安全呢???
涉及到了修改问题,所以可能无法保证创建对象的唯一性
假如n个线程同时调用那就会创建出n个对象,这就不满足我们的单例模式所以要进行一些加锁操作
要对整个进行加锁,保证其是原子操作
否则如果只在
进行加锁,还是会创建出很多个对象
优化
可以进行一小部分的优化
由于加锁解锁操作其实也是很需要时间开销,所以我们可以提前判断一下instance是否为null,如果为null就不需要进行加锁解锁操作,因为此时单纯的读取实例化对象没有涉及到修改,此时是处于线程安全的
package threading;
class SingletonLazy{
private static SingletonLazy instance;
public static SingletonLazy getInstance(){
if (instance == null) {//此处是为了判断对象是否为null,如果为null直接返回无需进行加锁操作节省时间
synchronized (SingletonLazy.class) {
if(instance == null){
instance = new SingletonLazy();
}
}
}
return instance;
}
private SingletonLazy(){}
}
public class ThreadDemo11 {
}
极小概率的一个细节
创建实例化对象的过程中有可能会触发指令重排序
在之前的文章已经说过,实例化对象也可以分成三步骤
1) 创建内存
2) 调用构造方法
3) 把内存地址给引用就是instance
如果触发指令重排序,就会先执行3,再执行2,如果系统调度给线程t1,发现条件不成立是非空的,此时其实还并没有调用构造方法,直接返回实例化对象的引用,就有可能出bug
所有可以加上volatile防止指令重排序
package threading;
class SingletonLazy{
volatile private static SingletonLazy instance;//防止极小概率指令重排序
public static SingletonLazy getInstance(){
if (instance == null) {//此处是为了判断对象是否为null,如果为null直接返回无需进行加锁操作节省时间
synchronized (SingletonLazy.class) {
if(instance == null){
instance = new SingletonLazy();
}
}
}
return instance;
}
private SingletonLazy(){}
}
public class ThreadDemo11 {
}
总结
饿汉模式 : 天生就是安全的,只是进行读操作
懒汉模式 : 不安全,有读也有写
1) 加锁,将 new 和 if 变成原子操作
2) 再次判断 if ,避免不必要的加锁节省开销
3) 使用volatile防止指令重排序发生,保证拿到的一定是完整的实例化对象