单例模式与线程安全

单例模式,顾名思义就是一个类只允许产生一个实例化对象,所以这对这个对象的产生方式获得方式以及这个类的构造函数都有一定的特殊要求。
首先是类本身的构造函数私有化,此时外部就不能够产生新的实例化对象,类的实例化对象只能够在类里面产生,然后再调用。
然后再类的里面产生一个私有的static方法来产生唯一的一个实例化对象。
最后再提供一个公有的static方法来取得此实例化对象。
主要思路流程大致如此,用代码实现此单例模式也有多种形式。
第一种形式:饿汉式单例模式

class Singleton1 {
    //此处static是为了能够让getInstance()方法的返回值对象直接通过类名调用
    private final static Singleton1 instance = new Singleton1();

    private Singleton1() {

    }

    //此处static是为了能够让外部类通过类名直接调用此方法
    public static Singleton1 getInstance() {
        return Singleton1.instance;
    }
}

测试用例:

public static void main(String[] args) {
        //此处获得对象只能直接调用类的公有方法来获取
        Singleton1 instance = Singleton1.getInstance();
        instance.print();
    }

第二种形式:静态内部类形式的饿汉式单例模式
这种方式需要把这个实例化对象封装在一个静态内部类里面,形式不一样,但是效果一样。

class Singleton2 {
    private static class inner {
        final static Singleton2 instance = new Singleton2();
    }

    private Singleton2() {

    }

    public static Singleton2 getInstance() {
        return inner.instance;
    }

    public void print() {
        System.out.println("This is only a instance!");
    }
}

上述两种方式由于是直接new的实例化对象,所以是线程安全的单例模式。
第三种形式:懒汉式单例模式
这种方式和上述两种方式的区别在于,它没有直接new出一个实例化对象,而是通过有延迟的判断来确定是否new一个实例化对象,所以也便产生的线程安全问题。

class Singleton3 {
    private static Singleton3 instance;

    private Singleton3() {

    }

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

    public void print() {
        System.out.println("This is only a instance!");
    }
}

以下来测试懒汉式单例模式是线程不安全的。
创建一个线程测试类:

class RunnableTest implements Runnable {

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

创建一个测试用例:

public static void main(String[] args) {
        Runnable runnable = new RunnableTest();
        new Thread(runnable, "t1").start();
        new Thread(runnable, "t2").start();
        new Thread(runnable, "t3").start();
        new Thread(runnable, "t4").start();
    }

结果如下:
在这里插入图片描述
显然,这里产生了多个实例化对象!
所以,为了使其成为线程安全的单例模式,我们需要对其进行部分代码的修改。
以下给出三种常见的修改方式。
第一种方式:同步方法

class Singleton4 {
    private static Singleton4 instance;

    private Singleton4() {

    }

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

    public void print() {
        System.out.println("This is only a instance!");
    }
}

第二种方式:同步class对象

class Singleton5 {
    private static Singleton5 instance;

    private Singleton5() {

    }

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

    public void print() {
        System.out.println("This is only a instance!");
    }
}

第三种方式:Lock锁

class Singleton6 {
    private static Singleton6 instance;
    private static Lock instanceLock = new ReentrantLock();

    private Singleton6() {

    }

    public static Singleton6 getInstance() {
        instanceLock.lock();
        if (instance == null) {
            instance = new Singleton6();
        }
        instanceLock.unlock();
        return instance;
    }

    public void print() {
        System.out.println("This is only a instance!");
    }
}

结果如下:
在这里插入图片描述
以上,便是几种常见的单例模式以及其线程安全性问题。
虽然上述三种方式可以使其变成线程安全的单例模式,但是这三种方式效率都不高,
下面给出一种效率更高的双重检验锁模式:

class Singleton9 {
    private volatile static Singleton9 instance;
    private Singleton9() {

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

上述的几种方式都能解决线程不安全的问题,但是都不是最安全的获得单例的方式,因为通过反射的手段利用setAccessible()方法破坏单例模式得到多例,下面给出一个测试案例:

class RunnableTest implements Runnable {

    @Override
    public void run() {
        Class cls = Singleton9.class;
        try {
            Constructor constructor = cls.getDeclaredConstructor();
            constructor.setAccessible(true);
            try {
                System.out.println(constructor.newInstance().hashCode());
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

public class TestDemo28 {
    public static void main(String[] args) {
        //Singleton8.ser();
        Runnable runnable = new RunnableTest();
        new Thread(runnable, "t1").start();
        new Thread(runnable, "t2").start();
        new Thread(runnable, "t3").start();
        new Thread(runnable, "t4").start();
    }
}

结果如下:
在这里插入图片描述
而最为安全妥善的创建单例的方式是枚举,枚举本身的设定就是为了用于创建多例甚至是单例。(推荐使用)

enum Singleton {
    instance;

    Singleton() {

    }
}

强行进行反射破坏

System.out.println(Singleton.instance.hashCode());
        //Singleton singleton = new Singleton();默认私有的构造方法
        Class clz =  Singleton.class;
        try {
            Constructor constructor = clz.getDeclaredConstructor();
            constructor.setAccessible(true);
            try {
                System.out.println(constructor.newInstance().hashCode());
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

结果如下:
在这里插入图片描述
最后,再叙述一下单例模式的两个优势:
1.减少创建Java实例所带来的系统开销。
2.便于系统跟踪单个Java实例的生命周期、实例状态等。

发布了49 篇原创文章 · 获赞 18 · 访问量 4364

猜你喜欢

转载自blog.csdn.net/asd0356/article/details/92785159