单例模式与多线程

1.饿汉模式

  该模式指调用方法前,实例已经被创建了。

/**
 * @author MM
 * @create 2019-03-04 16:39
 **/
public class MyObject {
    //立即加载模式 
    private static MyObject myObject = new MyObject();

    public MyObject() {
    }

    public static MyObject getInstance(){
        //
        return myObject;
    }
}

  

/**
 * @author MM
 * @create 2019-03-04 16:42
 **/
public class SingletonThread1 extends Thread {

    @Override
    public void run() {
        System.out.println(MyObject.getInstance().hashCode());
    }

    public static void main(String[] args) {
        SingletonThread1 t1 = new SingletonThread1();
        SingletonThread1 t2 = new SingletonThread1();
        SingletonThread1 t3 = new SingletonThread1();

        t1.start();
        t2.start();
        t3.start();
    }
}

  

该模式线程安全。

2. 懒汉模式(延迟加载)

  所谓延迟加载就是在调用获取实例方法时实例才被创建,常见的实例办法就是在获取实例时进行new 对象。

/**
 * @author MM
 * @create 2019-03-04 16:39
 **/
public class MyObject {
    //延迟加载模式
    private static MyObject myObject;

    public MyObject() {
    }

    public static MyObject getInstance(){
        if(myObject == null){
            myObject = new MyObject();
        }
        return myObject;
    }
}

  修改上面myObject代码,继续执行后结果,粗看结果是正确的,但稍微再次修改一下。

public class MyObject {
    //延迟加载模式
    private static MyObject myObject;

    public MyObject() {
    }

    public static MyObject getInstance() {
        try {
            if (myObject == null) {
                Thread.sleep(1000);
                myObject = new MyObject();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return myObject;
    }
}

  

可见这种写法存在线程安全问题。

解决方案:

  1):synchronized同步方法

  

    public static synchronized MyObject getInstance() {
        try {
            if (myObject == null) {
                Thread.sleep(1000);
                myObject = new MyObject();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return myObject;
    }

  这种方法虽然能获取正确的结果,但这种方法效率上稍微有些低下,因为整个方法同步,下一个线程要获得对象,需等待上一个线程释放锁后才可以继续执行。

  2):同步代码块

    a:如果直接将整个代码块同步其实效率和同步方法时一样的

    

    public static MyObject getInstance() {
        synchronized (MyObject.class){
            try {
                if (myObject == null) {
                    Thread.sleep(1000);
                    myObject = new MyObject();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return myObject;
    }

  

    b:针对某些重要代码进行单独同步

    public static MyObject getInstance() {
        try {
            if (myObject == null) {
                Thread.sleep(1000);
                synchronized (MyObject.class) {
                    myObject = new MyObject();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return myObject;
    }

  

  只对创建实例的代码加锁,结果还是不正确的。

  3):使用双重检测锁

    public static MyObject getInstance() {
        try {
            if (myObject == null) {
                Thread.sleep(1000);
                synchronized (MyObject.class) {
                    if(myObject == null){
                        myObject = new MyObject();//关键部分
                    }
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return myObject;
    }

  

  表明上能达到线程安全。实际写法还是不对。

  因为在 new MyObject() 的过程中,并不是一个原子操作,是可以进一步拆分为:

    1.分配内存空间

    2.初始化对象

    3.设置 instance 指向刚分配的内存

  但在jvm内部中如果经过指令重排后的结果可能为,1,3,2 那么在多线程环境中可能存在第一判断实例存在,但实际还未初识化的情况。

  要解决这种问题,可以用 volatile 关键字防止指令重排。

  private volatile MyObject myObject;

  4):静态内部类方法

    

public class MyObject {

    public MyObject() {
    }
    //静态内部类模式
    public static class MyObjectInstance{
        private static MyObject myObject = new MyObject();
    }

    public static MyObject getInstance() {

        return MyObjectInstance.myObject;
    }
}

  静态内部类能解决线程安全问题,但如果是遇到序列化对象时,使用这种默认方式运行得到的结果还是多实例的。

public class MyObject implements Serializable{

    private static final long serialVersionUID = -245041196348963545L;

    public MyObject() {
    }
    //静态内部类模式
    public static class MyObjectInstance{
        private static MyObject myObject = new MyObject();
    }

    public static MyObject getInstance() {

        return MyObjectInstance.myObject;
    }
}

  

public class SingletonSerializalbe {

    public static void main(String[] args) {
        MyObject myObject = MyObject.getInstance();

        //序列化
        try {
            FileOutputStream fos = new FileOutputStream(new File("D:\test.txt"));
            ObjectOutputStream outputStream = new ObjectOutputStream(fos);
            outputStream.writeObject(myObject);
            outputStream.close();
            fos.close();
            System.out.println(myObject.hashCode());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        //反序列化
        try {
            FileInputStream fins = new FileInputStream(new File("D:\test.txt"));
            ObjectInputStream inputStream = new ObjectInputStream(fins);
            MyObject myObject1 = (MyObject) inputStream.readObject();
            inputStream.close();
            fins.close();
            System.out.println(myObject1.hashCode());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

  

可见反序列化后的对象和原来的对象是不一致,解决需要在对象中添加readResolve()方法 

public class MyObject implements Serializable{

    private static final long serialVersionUID = -245041196348963545L;

    public MyObject() {
    }
    //静态内部类模式
    public static class MyObjectInstance{
        private static MyObject myObject = new MyObject();
    }

    public static MyObject getInstance() {

        return MyObjectInstance.myObject;
    }

    protected Object readResolve(){
        System.out.println("readResolve...");
        return MyObjectInstance.myObject;
    }
}

  

对于Serializable and Externalizable classes,方法readResolve允许class在反序列化返回对象前替换、解析在流中读出来的对象。实现readResolve方法,一个class可以直接控制反序化返回的类型和对象引用。 
方法readResolve会在ObjectInputStream已经读取一个对象并在准备返回前调用。ObjectInputStream 会检查对象的class是否定义了readResolve方法。如果定义了,将由readResolve方法指定返回的对象。
5):使用static代码块实现单例模式
  
public class MyObject implements Serializable{

    private static final long serialVersionUID = -245041196348963545L;

    public MyObject() {
    }
    private static MyObject instance = null;
    static {
        instance = new MyObject();
    }

    public static MyObject getInstance() {

        return instance;
    }
}

  6):使用枚举类实现单例

  


public class MyObject {


public enum MyObjectEnum {

MY_OBJECT_ENUM;
private MyObject instance = null;
MyObjectEnum() {
this.instance = new MyObject();
}


public MyObject getInstance(){
return instance;
}
}

public static MyObject getInstance(){
return MyObjectEnum.MY_OBJECT_ENUM.getInstance();
}
}
 
public class SingletonThread1 extends Thread {

    @Override
    public void run() {
        System.out.println(MyObject.getInstance().hashCode());
    }

    public static void main(String[] args) {
        SingletonThread1 t1 = new SingletonThread1();
        SingletonThread1 t2 = new SingletonThread1();
        SingletonThread1 t3 = new SingletonThread1();

        t1.start();
        t2.start();
        t3.start();
    }
}

枚举和static块类似,天然能保证线程安全。初始化时构造函数先被执行,该方法由jvm保证同步,效率很高。

原文地址:https://www.cnblogs.com/gcm688/p/10472194.html