单例模式与多线程

一、单例模式

1.1 立即加载/饿汉模式

立即加载就是使用类的时候已经创建完毕

立即加载:

public class single {
    static User user = new User();
    public static User getInstance(){
       return user; 
    }
}

测试:

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

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new MyThread().start();
        }
    }
}

结果:

610025186
610025186
610025186

1.2 延迟加载/懒汉模式

延迟加载就是调用ge方法时才开始创建对象

  • 同步方法保证线程安全,但是效率很低
public class single {
    static User user;
    public synchronized static User getInstance(){
        try {
            if(user == null){
                Thread.sleep(2000);
                user = new User();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
             return user;
    }
}
  • 同步代码块保证线程安全,但是效率也低,注意要使用类锁
public class single {
    static User user;

    public static User getInstance() {
        try {
            synchronized (single.class) {
                if (user == null) {
                    Thread.sleep(2000);
                    user = new User();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return user;
    }
}
  • 针对某些重要的代码进行单独同步
public class single {
    static User user;
    public static User getInstance() {
        try {
            if (user != null) {
            } else {
                Thread.sleep(2000);
                synchronized (single.class) {
                    user = new User();
                }

            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return user;
    }
}

结果:效率提升了,但是多线程下还是不安全

553602986
1354708602
964250181
  • 使用DCL双锁检查机制
public class single {
    static User user;
    public static User getInstance() {
        try {
            if (user != null) {
            } else {
                Thread.sleep(2000);
                synchronized (single.class) {
                    if(user == null)
                    user = new User();
                }

            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return user;
    }
}

作用:保证了不需要同步执行代码的异步执行性,又保证了单利的效果。

二、其他方法实现单例模式

2.1 静态内置类

public class single {
    public static class InSingle{
        static User user = new User();
        public static User getInstance(){
            return user;
        }
    }
}

和普通的饿汉模式差不多,只是多了一层包裹

2.2 序列化与反序列化

静态内置类如果遇到序列化对象时,还是会出现线程安全的问题
过当序列化遇到单例时,里边就有了个问题:从内存读出而组装的对象破坏了单例的规则。单例是要求一个JVM中只有一个类对象的,而现在通过反序列,一个新的对象克隆了出来。
我们必须得使用readResolve()方法,这样,当JVM从内存中反序列化地"组装"一个新对象时,就会自动调用这个 readResolve方法来返回我们指定好的对象了,单例规则也就得到了保证。

  • 序列化内置类
public class Single implements Serializable {
    public static final Long serialVersionUID = 122L;
    public static class InSingle {
        public static final Single single = new Single();
    }
        private Single(){

        }
        public static Single getInstance(){
            return InSingle.single;
        }
 }
  • 反序列化
public class test {
    public static void main(String[] args) {
        try {
            User instance = single.InSingle.getInstance();
            FileOutputStream fileOutputStream = new FileOutputStream(new File("instance.txt"));
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(instance);
            objectOutputStream.close();
            fileOutputStream.close();
            System.out.println(instance.hashCode());
            FileInputStream fileInputStream = new FileInputStream(new File("instance.txt"));
            ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
            User object = (User) inputStream.readObject();
            inputStream.close();
            fileInputStream.close();
            System.out.println(object.hashCode());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

输出:

1595428806
1469821799

对象在序列化之前和序列化后从文件读出已经不是同一个对象了。借助OblectInputStream和ObjectOutputStream,存储和读取参与序列化的类。

  • 解决方法
public class Single implements Serializable {
    public static final Long serialVersionUID = 122L;
    public static class InSingle {
        public static final Single single = new Single();
    }
        private Single(){

        }
        public static Single getInstance(){
            return InSingle.single;
        }
    public  Object readResolve() throws ObjectStreamException{
        System.out.println("调用了一个奇怪的方法");
        return InSingle.single;
    }
}

原理:我们必须得使用readResolve()方法,这样,当JVM从内存中反序列化地"组装"一个新对象时,就会自动调用这个 readResolve方法来返回我们指定好的对象了,单例规则也就得到了保证。

  • 最好是实现自己的单利模式,尽量别实现其他类的单例模式,可能会出现意想不到的错误。

2.3 使用static代码块

public class Single {
    public static Single single = null;
    static {
        single = new Single();
    }
    public static Single getInstance(){
        return single;
    }
}

2.4 使用枚举数据类型

  • 枚举类型和静态代码块的特征相似,在使用枚举类时,构造方法会被自动调用,也可以应用这个特性实现单例模式。
  • 枚举中的一个元素的构造方法只会调用一次
  • 枚举中的元素是枚举类型的。

内置枚举类:

public class SingGood {
    public static enum Goal{
        Interface;
        private User user;
        private Goal(){
            user = new User();
        }
        public User getInstance(){
            return user;
        }
    }
    public static User getInstance(){
        return Goal.Interface.getInstance();
    }
}

测试:

public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(SingGood.getInstance().hashCode());
    }

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new MyThread().start();
        }
    }
}

结果:

973798264
973798264
973798264
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值