Java多线程(6)

Java多线程(6)

七种单例设计模式的设计

饿汉式

public class Singleton {

//    实例变量
    private byte[] data = new byte[1024];

//    定义实例对象的时候直接初始化
    private static Singleton instance = new Singleton();

//    私有构造函数
    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }
}


饿汉式的关键在于instance作为类变量并且直接得到了初始化,如果主动使用Singleton类,那么instance实例将会直接完成创建

insta作为类变量在类初始化的过程中会被收集劲< clinit >()方法中,该方法能够百分之百保证同步,也就是instance在多线程的情况下不可能被实例化两次

懒汉式

public final class Singleton {
    
    private byte[] data = new byte[1024];
    
//    定义实例,但是不直接初始化
    private static Singleton instance = null;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if(null == instance){
            instance = new Singleton();
        }
        return instance;
    }
}


懒汉式就是使用类实例的时候再去创建

Singleton.class被初始化的时候instance并不会被实例化,但是getInstance方法放在多线程环境下分析,则会导致instance被实例化不止一次,并不能保证单例的唯一性

懒汉式 + 同步方法

public class Singleton {
    
    private byte[] data = new byte[1024];
    
    private static Singleton instance = null;

    private Singleton() {
    }

//    加入同步控制,每次只有一个线程能够进入
    public synchronized static Singleton getInstance() {
        if (null==instance){
            instance = new Singleton();
        }
        return instance;
    }
}


考虑到懒汉式无法保证实例的唯一性,此处使用synchronize关键字来修饰getInstance方法,来保证数据的同步性,但是这种方法性能低下。

Double - Check

public class Singleton {

    private byte[] data = new byte[1024];

    private static Singleton instance = null;

    private Singleton() {
    }

    public static Singleton getInstance() {

//        当instance为null,进入同步代码块,避免了每次都要进入
        if (null == instance){


//            只有一个线程能够获得Singleton.class关联的锁
            synchronized (Singleton.class){
                if (null == instance){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}


双重检查机制首次初始化的时候加锁,之后则允许多个线程同时进行getInstance调用获取类的实例

当两个线程发现null == instance时候,只有一个线程有资格进入同步代码块,完成instance的实例化,随后线程发现null == instance不成立时无需进行任何动作

Volatile + Double - Check

上述Double - Check写法看似解决了问题,但是有个很大的隐患。实例化对象的那行代码(instance = new Singleton();),实际上可以分解成以下三个步骤:

  • 分配内存空间

  • 初始化对象

  • 将对象指向刚分配的内存空间

但是有些编译器为了性能的原因,可能会将第二步和第三步进行重排序,顺序就成了:

  • 分配内存空间

  • 将对象指向刚分配的内存空间

  • 初始化对象

这种情况下对应到就是singleton已经不是null,而是指向了堆上的一个对象,但是该对象却还没有完成初始化动作。当后续的线程发现singleton不是null而直接使用的时候,就会出现意料之外的问题

可以使用volatile关键字修饰变量来解决无序写入产生的问题,因为volatile关键字的一个重要作用是禁止指令重排序,即保证不会出现内存分配、返回对象引用、初始化这样的顺序,从而使得双重检测真正发挥作用


public class Singleton {

    private byte[] data = new byte[1024];

    private volatile static Singleton instance = null;

    private Singleton() {
    }

    public static Singleton getInstance() {

//        当instance为null,进入同步代码块,避免了每次都要进入
        if (null == instance){


//            只有一个线程能够获得Singleton.class关联的锁
            synchronized (Singleton.class){
                if (null == instance){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Holder方式

public class Singleton {
    
    private byte[] data = new byte[1024];

    private Singleton() {
    }
    
//    在静态内部类中持有Singleton实例,并且直接进行初始化
    private static class Holder{
        private static Singleton instance = new Singleton();
    }
    
    
    public static Singleton getInstance(){
        return Holder.instance;
    }
}


Holder方式完全借助了类加载的特点

此方式又可以保证数据的同步性,也能保证内存的可见性,指令顺序性,原子性

枚举方式

public enum Singleton {
    
    INSTANCE;
    
    private byte[] data = new byte[1024];
    
    Singleton()
    {
        System.out.println("INSTANCE will be 初始化");
    }
    
    public static void method(){
//        调用该方法会主动使用Singleton,INSTANCE将被实例化
    }
    
    public static Singleton getInstance(){
        return INSTANCE;
    }
}

枚举类型不允许继承,同样是线程安全的且只能被实例化一次

发布了189 篇原创文章 · 获赞 58 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/Coder_py/article/details/104043506
今日推荐