多线程与单例模式

立即加载/饿汉模式

立即加载是指在使用类的时候对象已经创建完毕,常见的实现方法是直接new实例化。在立即加载/饿汉模式中,调用方法前,实例已经被创建了,下面来看一下实现代码:

package com.single.thread;

public class SingleInstance {
    private static SingleInstance instance=new SingleInstance();
    private SingleInstance(){}
    public static SingleInstance getInstance(){
        return instance;
    }
}
class test{
    public static void main(String[] args) {
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(SingleInstance.getInstance().hashCode());
            }
        });
        Thread thread1 = new Thread(thread);
        Thread thread2 = new Thread(thread);
        Thread thread3 = new Thread(thread);

        thread1.start();
        thread2.start();
        thread3.start();
    }
}
测试结果:
2084620955
2084620955
2084620955

控制台输出的结果看出hashcode是一个值完成了立即加载的单例模式。但是缺点是不能拥有其他的实例变量,因为getinstance方法不是同步的,所以可能出现非线程安全问题。

延迟加载/懒汉模式

延迟加载是指调用方法的时候对象才实例化。

package com.single.thread;

public class SingleInstance2 {
    private static SingleInstance2 instance;
    private SingleInstance2(){}
    public static SingleInstance2 getInstance(){
        if(instance==null){
            instance=new SingleInstance2();
        }
        return instance;
    }
}

class test2{
    public static void main(String[] args) {
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(SingleInstance2.getInstance().hashCode());
            }
        });
        Thread thread1 = new Thread(thread);
        Thread thread2 = new Thread(thread);
        Thread thread3 = new Thread(thread);

        thread1.start();
        thread2.start();
        thread3.start();
    }
}
结果:
34369211
1712447624
1300234150

控制台输出的hashcode不同说明创建了三个对象并不是单例的。

懒汉模式的解决方案

只需要在getinstance方法上加上同步关键字synchronized
package com.single.thread;

public class SingleInstance2 {
    private static SingleInstance2 instance;
    private SingleInstance2(){}
    public synchronized static SingleInstance2 getInstance(){
        if(instance==null){
            instance=new SingleInstance2();
        }
        return instance;
    }
}

class test2{
    public static void main(String[] args) {
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(SingleInstance2.getInstance().hashCode());
            }
        });
        Thread thread1 = new Thread(thread);
        Thread thread2 = new Thread(thread);
        Thread thread3 = new Thread(thread);

        thread1.start();
        thread2.start();
        thread3.start();
    }
}
结果:

1746582977
1746582977
1746582977

此方法加上synchronized关键字得到了相同的实例对象,但是这种方法效率低,是同步运行的。下一个线程必须等上一个线程释放锁才能进入此方法。
能否使用同步代码块来解决下面来看一个案例

package com.single.thread;

public class SingleInstance3 {
    private static SingleInstance3 instance;
    private SingleInstance3(){}
    public  static SingleInstance3 getInstance(){
        try {
            if(instance==null){
                Thread.sleep(200);
                synchronized (SingleInstance3.class) {
                    instance = new SingleInstance3();
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }

        return instance;
    }
}

class test3{
    public static void main(String[] args) {
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(SingleInstance3.getInstance().hashCode());
            }
        });
        Thread thread1 = new Thread(thread);
        Thread thread2 = new Thread(thread);
        Thread thread3 = new Thread(thread);

        thread1.start();
        thread2.start();
        thread3.start();
    }
}
结果:
1877212914
847086262
1688623490

此方法使用同步synchronized语句块对实例化对象的关键代码进行同步,运行效率得到了提升,但是从结果上来看并没有解决多线程问题。下面使用
双检查锁机制来解决多线程下加载饿汉单例模式。

package com.single.thread;

public class SingleInstance4 {
    private volatile static SingleInstance4 instance;
    private SingleInstance4(){}
    public  static SingleInstance4 getInstance(){
        try {
            if(instance==null){
                Thread.sleep(200);
                synchronized (SingleInstance4.class) {
                    if(instance==null)
                    instance = new SingleInstance4();
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }

        return instance;
    }
}

class test4{
    public static void main(String[] args) {
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(SingleInstance4.getInstance().hashCode());
            }
        });
        Thread thread1 = new Thread(thread);
        Thread thread2 = new Thread(thread);
        Thread thread3 = new Thread(thread);

        thread1.start();
        thread2.start();
        thread3.start();
    }
}
结果:
1688623490
1688623490
1688623490

使用volatile关键字可以使instance变量在多线程间达到可见性,另外也会禁止指令重排,因为 instance = new SingleInstance4()代码在内存部分为三个部分
1)memory=allocate(); //分配对象的内存空间
2) ctorInstance(memory); //初始化对象
3)instance=memory; //设置instance指向刚分配的内存地址

JIT编译器有可能将这三个步骤重排序成:

1)memory=allocate(); //分配对象的内存空间
2) instance=memory; //设置instance指向刚分配的内存地址
3)ctorInstance(memory); //初始化对象
这时候将会出现以下情况:虽然构造方法还没有执行,但是instance对象有了内存地址,值不是null,当访问instance对象中的实量还是默认数据类型的默认值。

使用静态内部类实现单例模式

public class SingleInstance5 {

    private SingleInstance5(){}
    private  static class InnerInstance{
        private static SingleInstance5 instance=new SingleInstance5();
    }
    public static SingleInstance5 getInstance(){

        return InnerInstance.instance;
    }
}

class test5{
    public static void main(String[] args) {
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(SingleInstance5.getInstance().hashCode());
            }
        });
        Thread thread1 = new Thread(thread);
        Thread thread2 = new Thread(thread);
        Thread thread3 = new Thread(thread);

        thread1.start();
        thread2.start();
        thread3.start();
    }
}
结果:
38910761
38910761
38910761

从控制台输出的结果来看可以知道静态内部类可以实现单例模式。

序列化与反序列化的单列模式实现

public class User {

}
package com.single.xue;

import java.io.ObjectStreamException;
import java.io.Serializable;

public class MyObject implements Serializable {
    private static final long serialVersionUID=666L;
    public static User user=new User();
    private static MyObject myObject=new MyObject();
    private MyObject(){

    }
    public static MyObject getInstance(){
        return myObject;
    }
    //该方法在反序列化时不创建新的instance对象
    protected Object readResolve() throws ObjectStreamException {
        System.out.println("该方法被调用了");
        return MyObject.myObject;
    }
}

测试类:

import java.io.*;

public class Test {
    public static void main(String[] args) {
        MyObject instance = MyObject.getInstance();
        FileOutputStream fileOutputStream=null;
        ObjectOutputStream objectOutputStream=null;
        FileInputStream fileInputStream=null;
        ObjectInputStream objectInputStream=null;
        try {
            System.out.println("序列化——instance=" + instance.hashCode() + "\t" +
                    "user=" + MyObject.user.hashCode());
            fileOutputStream = new FileOutputStream(new File("e:\\myObject.txt"));
            objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(instance);
            fileOutputStream.close();
            objectOutputStream.close();
        }catch (Exception e){
            e.printStackTrace();
        }
        try{
            fileInputStream=new FileInputStream(new File("e:\\myObject.txt"));
            objectInputStream=new ObjectInputStream(fileInputStream);
            MyObject object = (MyObject)objectInputStream.readObject();
            fileInputStream.close();
            objectInputStream.close();
            System.out.println("序列化——instance=" + instance.hashCode() + "\t" +
                    "user=" + MyObject.user.hashCode());
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
结果:
序列化——instance=557041912	user=1134712904
该方法被调用了
序列化——instance=557041912	user=1134712904

静态代码块实现单例模式

package com.single.thread;

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

class test6{
    public static void main(String[] args) {
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(SingleInstance6.getInstance().hashCode());
            }
        });
        Thread thread1 = new Thread(thread);
        Thread thread2 = new Thread(thread);
        Thread thread3 = new Thread(thread);

        thread1.start();
        thread2.start();
        thread3.start();
    }
}
结果:
566612817
566612817
566612817

静态代码块在使用类的时候就已经执行,所有可以应用静态代码块的这个特性来实现单例。

使用enum枚举数据类型来实现单例模式

public class MyObject {
    public enum myEnumSingleton{
        users;
        private User user;

        private myEnumSingleton() {
            user=new User();
        }
        public User getUser(){
            return user;
        }
    }
    public static User getUser(){
        return myEnumSingleton.users.getUser();
    }
}
class User{

}
class Tests{
    public static void main(String[] args) {
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 2; i++) {
                    System.out.println(MyObject.getUser().hashCode());
                }
            }
        });
        Thread thread1 = new Thread(thread);
        Thread thread2 = new Thread(thread);
        Thread thread3 = new Thread(thread);

        thread1.start();
        thread2.start();
        thread3.start();
    }
}
结果:
370102716
370102716
370102716
370102716
370102716
370102716

enum枚举类型的特性和静态代码块的特性相似,在使用枚举类时,构造方法会被自动调用,所有可以用来实现单例模式。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

活跃的咸鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值