单例模式和多线程

  • 单例模式和多线程
    1、饿汉模式
    饿汉模式:在调用get方法之前,已经将对象准备完成。有急迫着急的意境。
饿汉模式
class MyObj0{
    public volatile static MyObj0 obj = new MyObj0();
    //单例模式只能通过get方式合法获取对象。不允许外部调用构造方式实例对象。
    private MyObj0(){

    }
    public static MyObj0 getObjInstance(){
        return obj;
    }
}
多线程调用
public static void main(String[] args){
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(MyObj0.getObjInstance().hashCode());
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(MyObj0.getObjInstance().hashCode());
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(MyObj0.getObjInstance().hashCode());
            }
        }).start();
    }

由于第一次调用getObjInstance()方法之前就已经创建好了对象,三个线程获取的对象是用一个。
1、懒汉模式
懒汉模式:在使用get方法的时候,才去创建对象。

class MyObj{
    public volatile static MyObj obj;
    private MyObj(){

    }
    public static MyObj getObjInstance(){
        try {
            if (obj == null) {
                //模拟创建对象前的准备
                Thread.sleep(2000);
                obj = new MyObj();
            } else {
            }
        } catch (InterruptedException e) {

        }
        return obj;
    }
}

此时多线程调用此方法。容易出现线程安全问题。
在判断obj 是否为空时容易出现线程安全问题:
第一个线程判断obj为空进入方法,对象赋值前失去时间片,第二个线程进来判断,再次通过。再次创建对象。

解决方法:
1、使用synchronized修饰方法,或者使用synchronized块包裹判断语句。

class MyObj2{
    public volatile static MyObj2 obj;
    private MyObj2(){

    }
    public static MyObj2 getObjInstance() {
        try {
            synchronized (MyObj2.class) {
                if (obj == null) {
                    Thread.sleep(2000);
                    obj = new MyObj2();
                } else {
                }
            }
        } catch (Exception e) {
        }
        return obj;
    }
}
class MyObj2{
    public volatile static MyObj2 obj;
    private MyObj2(){

    }
    public synchronized static MyObj2 getObjInstance() {
        try {
            if (obj == null) {
                Thread.sleep(2000);
                obj = new MyObj2();
            } else {
            }

        } catch (Exception e) {
        }
        return obj;
    }
}
多线程效率低

2、使用DCL双检查锁机制

class MyObj3{
    public volatile static MyObj3 obj;

    public static MyObj3 getObjInstance(){
        try {
            if (obj != null) {

            } else {
                Thread.sleep(2000);
                synchronized (MyObj3.class) {
                    if (obj == null) {
                        obj = new MyObj3();
                    }
                }
            }
        } catch (Exception e) {
        }
        return obj;
    }
}
增加同步效率。

3、使用类级内部类的形式初始化对象
使用static修饰的内部类为类级内部类,只有在第一次调用的时候才会被装载。

class InnerMyObject {
    private static class MyObjHandler {
        private static InnerMyObject obj = new InnerMyObject();
    }
    private InnerMyObject(){

    }
    public InnerMyObject getObjectIncetance(){
        return MyObjHandler.obj;
    }
}
由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时,JVM已经隐含的为您执行了同步,这些情况下就不用自己再来进行同步控制了。

4、序列化和反序列化的单例模式
在单例模式中添加readResolve()方法

public class Main {
    public static void main(String[] args){
        writeObject();
        try {
            Thread.sleep(2000);
        } catch (Exception e) {
        }
        readObject();
    }
    public static void writeObject(){
        try {
            MyObj object = MyObj.getObjInstance();
            FileOutputStream outputStream = new FileOutputStream(new File("a.txt"));
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
            objectOutputStream.writeObject(object);
            outputStream.close();
            objectOutputStream.close();
            System.out.println("输出完毕");
            System.out.println(object.hashCode());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void readObject(){
        try {
            FileInputStream inputStream = new FileInputStream(new File("a.txt"));
            ObjectInputStream ois = new ObjectInputStream(inputStream);
            MyObj obj = (MyObj)ois.readObject();
            inputStream.close();
            ois.close();
            System.out.println("读取完成");
            System.out.println(obj.hashCode());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
class MyObj implements Serializable{
    public volatile static MyObj obj;
    private MyObj(){

    }
    public static MyObj getObjInstance(){
        if (obj == null) {
            synchronized (MyObj.class) {
                if (obj == null) {
                    obj = new MyObj();
                }
            }
        }
        return obj;
    }
    //保证序列化反序列化后得到的仍是同一个对象
    private Object readResolve() {
        return obj;
    }
}

在没有填加readResolve()方法之前。看一下readObject()的实现:
readObject–>readObject0–>readOrdinaryObject

 Object obj;
        try {
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }
通过isInstantiable()方法判断这个类是否为serializable/externalizable
desc.newInstance():通过反射调用无参构造创建一个新对象。
serializable/externalizable:
1、Serializable序列化时不会调用默认的构造器,而Externalizable序列化时会调用默认构造器的!!! 
2、Serializable:一个对象想要被序列化,那么它的类就要实现 此接口,这个对象的所有属性(包括private属性、包括其引用的对象)都可以被序列化和反序列化来保存、传递。 
Externalizable:他是Serializable接口的子类,有时我们不希望序列化那么多,可以使用这个接口,这个接口的writeExternal()和readExternal()方法可以指定序列化哪些属性。

当该对象中有readResolve()方法时

//readResolveMethod 赋值
readResolveMethod = getInheritableMethod(
                        cl, "readResolve", null, Object.class);
//判断对象是否有readResolve方法。
boolean hasReadResolveMethod() {
        requireInitialized();
        return (readResolveMethod != null);
    }
//更新之前创建的对象
if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            //调用readResolve()方法
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                handles.setObject(passHandle, obj = rep);
            }
        }
反序列化时先判断对象中是否有readResolve()方法,如果有则调用该方法。如果没有则使用反射调用该对象的无参构造。

5、使用static代码块实现单例模式
static静态代码块在类加载器加载时被实例化,因此静态代码块中创建对象只会创建一次。

class MyObject implements{
    private static MyObject instance = null;
    private MyObject() {
    }
    static {
        instance = new MyObject();
    }
    public static MyObject getInstance() {
        return instance;
    }
}

在涉及序列化和反序列化时该单例模式也需要实现序列化接口和添加readResolve()方法。
6、使用枚举实现单例模式
使用枚举模式获取jdbc连接:自由序列化,线程安全,保证单例。

enum有且仅有private的构造器,防止外部的额外构造。
对于序列化和反序列化,因为每一个枚举类型和枚举变量在JVM中都是唯一的,即Java在序列化和反序列化枚举时做了特殊的规定,枚举的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法是被编译器禁用的,因此也不存在实现序列化接口后调用readObject会破坏单例的问题。
对于线程安全方面,类似于普通的饿汉模式,通过在第一次调用时的静态初始化创建的对象是线程安全的。
public class Main {
    public static void main(String[] args){
        Connection connction = ConnectionSingletion.getConnction();
    }
}
class ConnectionSingletion{
    enum  ConnectSingleton {
        connectFactory;
        private Connection connection;
        private ConnectSingleton(){
            String driver = "com.mysql.jdbc.Driver";
            String url = "jdbc:mysql://localhost:3306/test";
            String userName = "root";
            String password = "root";
            try {
                Class.forName(driver);
                connection = (Connection) DriverManager.getConnection(url,userName,password);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        public Connection getConnection(){
            return connection;
        }
    }
    public static Connection getConnction(){
        return ConnectSingleton.connectFactory.getConnection();
    }
}

部分内容参考自:
《java多线程编程核心技术》
Externalizable和Serializable
单例模式之静态代码块实现
枚举实现单例模式

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值