java 单例模式
1.饿汉式
饿汉式存在内存浪费问题,没用到的时候实例直接就初始化了。
package com.yang;
public class Hungry {
//单例模式构造函数必须私有
private Hungry(){
}
public static final Hungry hungry=new Hungry();//常量必须当时初始化
public static Hungry getInstance(){
return hungry;
}
}
2.DCL懒汉式
懒汉式存在多线程并发问题。
package com.yang;
public class LazyMan {
//私有构造
private LazyMan() {
}
private static LazyMan lazyman;
public static LazyMan getInstance(){
if(lazyman==null){
lazyman=new LazyMan();
}
return lazyman;
}
}
多线程来同时第一次getInstance时可能都进入了函数中的if语句,导致构造函数多次调用。我们加上双重检测锁。就是所谓得DCL懒汉式。
双重检测锁第一层可以提升效率,免得后续getInstance每次都要锁类,导致效率低下,第二层检测锁是为了避免单例模式在多线程下的冲突。另外还要给私有变量加上原子操作。
volatile是java关键字,是轻量级的同步。有三大特性:
- 保证可见性,即一个线程操作变量时,将主存中的变量值copy到自己线程的工作内存中,还没copy回去时,其他线程对该变量做出修改操作,此时本线程对该修改可见,
- 不保证原子行,即一个线程对内存中变量操作时,从主存复制到自己的内存中还没复制回去时,其他线程可以对该资源做出修改,不安全。要想解决可以靠LOCK、synchornized,而更好的方式时 juc.atomic下的原子类,比如 AtomicInteger。其底层是CAS原理效率极高。
- 禁止指令重排,防止编译器或者内存对我们的指令进行重新排序从而产生问题,原理是内存屏障。比如我们new一个对象的时候,1.分配内存空间、2.初始化对象、3.将引用指向该对象。而指令重排可能导致步骤为 1.3.2.这在我们的DCL懒汉式单例里面就会有问题。
public class LazyMan {
//私有构造
private LazyMan() {
}
//volatile禁止指令重排,一个线程创建lazyman时,可能由于指令重排,引用已经指向内存地址了,
//然而该内存对象还没初始化好,导致其他线程取到没初始化好的单例。
private volatile static LazyMan lazyman;
//双重检测锁
public static LazyMan getInstance(){
if(lazyman==null){
//拿到类锁,直接锁住该类
synchronized (LazyMan.class){
if(lazyman==null){
lazyman=new LazyMan();
}
}
}
return lazyman;
}
}
3.静态内部类
内部类可以分为静态内部类和非静态内部类,,静态内部类由static修饰,可以包含静态成员,而非静态内部类不能包含静态成员。
定义静态内部类的时候,不需要绑定在外部类的实例上,即不需要new这个静态内部类,把它当成静态成员看待。
静态内部类相当于静态方法,只能访问外部类的静态成员,而非静态内部类可以访问外部类的所有成员。
public class LazyMan {
//私有构造
private LazyMan() {
}
public static LazyMan getInstance(){
return innerClass.lazyman;
}
//静态内部类
public static class innerClass{
public static LazyMan lazyman=new LazyMan();
}
}