单例模式
单例简介
因进程需要,有时我们只需要某个类同时保留一个对象,不希望有更多对象。
单例模式的特点:
1.单例模式只能有一个实例。
2.单例类必须创建自己的唯一实例。
3.单例类必须向其他对象提供这一实例。
懒汉式
特点:
线程不安全,如果多线程同时访问,会创造出多个对象。
/**
* 通过提供一个静态的对象 SINGLE_TON_DEMO_1
* 利用private修饰的构造方法和getInstance()提供一个单例
*
* 缺点:线程不安全,如果多个线程同时访问,会构造出多个对象
*/
public class SingleTonDemo1 {
private static SingleTonDemo1 SINGLE_TON_DEMO_1 ;
private SingleTonDemo1(){}
public static SingleTonDemo1 getInstance(){
if (SINGLE_TON_DEMO_1==null) {
SINGLE_TON_DEMO_1 = new SingleTonDemo1();
}
return SINGLE_TON_DEMO_1;
}
}
实例化方法加synchronized的懒汉式
特点:
线程安全,但是效率不高。
由于并发并不是随时都在发生,大多数情况下这个锁占用的资源都浪费了。
/**
* 并发不是随时都在发生,大多数时候这个锁占用的资源都浪费了
* 虽然线程安全,但是效率不高
*/
public class SingleTonDemo2 {
private static SingleTonDemo2 singleTonDemo2;
private SingleTonDemo2(){}
public static synchronized SingleTonDemo2 getInstance(){
if (singleTonDemo2 == null) {
singleTonDemo2 = new SingleTonDemo2();
}
return singleTonDemo2;
}
}
饿汉式
特点:
线程安全
/**
* 线程安全
* 相比于静态方类,内存常驻
*/
public class SIngleTonDemo3 {
private static SIngleTonDemo3 sIngleTonDemo3 = new SIngleTonDemo3();
private SIngleTonDemo3(){}
public static SIngleTonDemo3 getInstance(){
return sIngleTonDemo3;
}
}
静态内部类加载
特点:
1.线程安全
2.使用静态内部类的好处是,不会随着类的加载而加载,而是在调用getInstance()方法时再加载,达到类似懒汉模式的效果,而这种方式是线程安全的
/**
* 线程安全
*
* 使用静态内部类的好处是,不会随着类的加载而加载,而是在调用getInstance()方法时
* 再加载,达到类似懒汉模式的效果,而这种方式是线程安全的
*/
public class SingleTonDemo4 {
public static class SingleTonHolder{
private static SingleTonDemo4 singleTonDemo4 = new SingleTonDemo4();
}
private SingleTonDemo4(){}
public static SingleTonDemo4 getInstance(){
return SingleTonHolder.singleTonDemo4;
}
}
枚举方法
特点:
1.线程安全
2.自由串行化
3.保证只有一个单例
4.Effective java 作者Josh Bloch提倡的方式
/**
* Effective java 作者Josh Bloch提倡的方式,解决了以下三个问题
* 一、自由串行化
* 二、保证只有一个单例
* 三、线程安全
*/
public enum SingleTonDemo5 {
INSTANCE;
public static void main(String[] args) {
SingleTonDemo5 instance = SingleTonDemo5.INSTANCE;
}
}
双重校验锁
特点:
1.通常线程安全,低概率线程不安全
2.并发情况下,可能会出现线程未拿到实例返回null的情况。
/**
* 通常线程安全,低概率线程不安全
*
* 当并发情况下
* 一、线程A进入getInstance()方法,此时单例还没有实例化,进入了锁定块
* 二、线程B进入getInstance()方法,此时单例还没有实例化,得以访问接下来代码块,但是代码块已经被线程A锁定
* 三、线程A进入下一判断,因为单例还没有实例化,所以实例化单例,实例化后退出代码块,解除锁定
* 四、线程B进入下一判断,此时单例已经被实例化,退出代码块,解除锁定
* 五、线程A拿到单例实例并返回,线程B未拿到实例返回null
*
*/
public class SingleTonDemo6 {
private static SingleTonDemo6 singleTonDemo6;
private SingleTonDemo6(){}
public static SingleTonDemo6 getInstance(){
if (singleTonDemo6 == null){
synchronized (SingleTonDemo6.class){
if (singleTonDemo6 == null){
singleTonDemo6 = new SingleTonDemo6();
}
}
}
return singleTonDemo6;
}
}
加volatile的双重校验锁
特点:
1.volatile关键字此处的作用是防止指令重排
/**
* volatile关键字此处的作用是防止指令重排,把singleTonDemo7声明为volatile后,对它的写操作就会
* 有一个内存屏障,这样在它赋值完成之前,都不会调用读操作。
*
* 注意:volatile阻止的不是singleTonDemo7 = new SingleTonDemo7(),而是在写操作完成之前,不会调用
* if (singleTonDemo7 == null)这个读操作
*/
public class SingleTonDemo7 {
private static volatile SingleTonDemo7 singleTonDemo7;
private SingleTonDemo7(){}
public static SingleTonDemo7 getInstance(){
if (singleTonDemo7 == null){
synchronized (SingleTonDemo7.class){
if (singleTonDemo7 == null){
singleTonDemo7 = new SingleTonDemo7();
}
}
}
return singleTonDemo7;
}
}
ThreadLocal
特点:
线程安全
隔离多个线程对数据的访问冲突
/**
* 线程安全
* ThreadLocal会为每个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突
*
*
*/
public class SingleTonDemo8 {
private static final ThreadLocal<SingleTonDemo8> THREAD_LOCAL =
new ThreadLocal<SingleTonDemo8>(){
@Override
protected SingleTonDemo8 initialValue() {
return new SingleTonDemo8();
}
};
private SingleTonDemo8(){}
public static SingleTonDemo8 getInstance(){
return THREAD_LOCAL.get();
}
}
CAS锁
特点:
线程安全
/**
* 线程安全
*
* CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
*更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
* CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去尝试更新
*
* 缺点:
* CPU开销较大
* CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性
*/
public class SingleTonDemo9 {
private static final AtomicReference<SingleTonDemo9> ATOMIC_REFERENCE = new AtomicReference<>();
private SingleTonDemo9(){}
public static final SingleTonDemo9 getInstance(){
// 相当于while(true)
for (;;){
SingleTonDemo9 currentDemo9 = ATOMIC_REFERENCE.get();
if (currentDemo9 != null){
return currentDemo9;
}
currentDemo9 = new SingleTonDemo9();
if (ATOMIC_REFERENCE.compareAndSet(null,currentDemo9)){
return currentDemo9;
}
}
}
public static void main(String[] args) {
SingleTonDemo9 testSingleTon = new SingleTonDemo9();
SingleTonDemo9 testSingleTon2 = new SingleTonDemo9();
System.out.println(testSingleTon == testSingleTon2); //false
}
}