线程安全的单例模式

单例设计模式之旅

  • 单例设计模式应用场景
  • 如何实现单例设计模式
  • 懒汉设计模式线程安全

1. 单例模式的应用场景

(1).单例模式就是类在程序运行的过程中只创建一个对象,同时该类会提供一个可以在全局范围内访问这个对象的static方法并且私有化该类的构造器,这个静态方法通常为命名为getInstance()。

(2).单例模式可以根据创建对象的时间分为饿汉模式和懒汉模式。如果一个类的实例在项目启动时不管使用不使用就被预先创建则称这种单例模式为饿汉模式;如果一个类的实例在启动时不预先创建而是在第一次调用时创建则称这种单例模式为懒汉模式.

(3).饿汉模式的优点是立即创建实例在首次调用时会节省时间,缺点是会延长项目启动的时间。懒汉模式优点是会节省项目启动的时间;缺点是会在首次调用时耗费时间,使用不当会出现线程安全问题。

(4).总结:通常饿汉模式用于较小的项目中,懒汉模式用于较大的项目中。两者的特点说明时间和空间是不可以兼得的。

2. 单例模式的实现案例

package csdn.qiang;
/**
 * 单例模式之饿汉模式
 */
public class SingletonA {
    private static SingletonA singleton = new SingletonA();
    private SingletonA(){}//私有化构造器防止外部创建对象
    public static SingletonA getInstance(){
        return singleton;
    }
    public static void main(String[] args) {//验证
        Thread t1 = new Thread(()->{
            System.out.println(
                SingletonA.getInstance().hashCode()
            );
        });
        Thread t2 = new Thread(()->{
            System.out.println(
                SingletonA.getInstance().hashCode()
            );
        });
        Thread t3 = new Thread(()->{
            System.out.println(
                SingletonA.getInstance().hashCode()
            );
        });
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果:

这里写图片描述

上面的运行结果表明:在三个不同的线程中获取的是同一个对象。


package csdn.qiang;
/**
 * 典型错误:单例模式之懒汉模式
 */
public class SingletonB {
    private static SingletonB singleton = null;
    private SingletonB(){}//私有化构造器防止外部创建对象
    public static SingletonB getInstance(){
        if(singleton==null)
            singleton = new SingletonB();
        return singleton;
    }
    public static void main(String[] args) {//验证
        Thread t1 = new Thread(()->{
            System.out.println(
                SingletonB.getInstance().hashCode()
            );
        });
        Thread t2 = new Thread(()->{
            System.out.println(
                SingletonB.getInstance().hashCode()
            );
        });
        Thread t3 = new Thread(()->{
            System.out.println(
                SingletonB.getInstance().hashCode()
            );
        });
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果

这里写图片描述

上面的运行结果表明:在三个不同的线程中获取的是同一个对象。表面上看,这三个线程确实是获取了同一个对象,其实,这样写是有bug的,只是在这里没有表现出来。没有表现出来的原因是三个线程去获取对象时在时间上具有一定的先后顺序,即当第一个线程创建并获取对象后其它线程才来获取对象发现对象已经被创建,这样后来的线程就直接获取对象就可以了而不需要去创建对象,这样三个线程获取的就是同一个对象了。

package csdn.qiang;

import java.util.concurrent.TimeUnit;
/**
 * 错误展示:单例模式之懒汉模式
 */
public class SingletonC {
    private static SingletonC singleton = null;
    private SingletonC(){}//私有化构造器防止外部创建对象
    public static SingletonC getInstance() 
        throws  InterruptedException{
            if(singleton==null){
                TimeUnit.SECONDS.sleep(3);
                singleton = new SingletonC();
            }
            return singleton;
    }
    public static void main(String[] args) 
        throws InterruptedException {//懒汉模式的验证
            Thread t1 = new Thread(()->{
                try {
                    System.out.println(
                        SingletonC.
                            getInstance().hashCode()
                    );
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });

            Thread t2 = new Thread(()->{
                try {
                    System.out.println(
                        SingletonC.
                            getInstance().hashCode()
                    );
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });

            Thread t3 = new Thread(()->{
                try {
                    System.out.println(
                        SingletonC.
                            getInstance().hashCode()
                    );
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果:

这里写图片描述

上面的运行结果表明:多个线程在访问同一个资源的时机往往是不确定的,并不能保证多个线程在需要获取对象时对象都经被创建。可能出现的情况是多个线程都来访问同一个对象,但是该对象并没有被创建,这样每一个线程都会主动去创建一个对象。这样就不能保证每一个线程获取的都是同一个对象。

3.懒汉模式线程安全
(1).相对低效的懒汉模式(粗颗粒度的synchronized)

package csdn.qiang;

import java.util.concurrent.TimeUnit;
/**
 * 相对低效且线程安全的懒汉模式
 */
public class SingletonD {
    private static SingletonD singleton = null;
    private SingletonD(){}//私有化构造器防止外部创建对象
    public synchronized static SingletonD 
        getInstance() throws InterruptedException{
        //使用synchronized锁定SingletonD.class
        if(singleton==null){
            TimeUnit.SECONDS.sleep(3);
            singleton = new SingletonD();
        }
        return singleton;
    }
    public static void main(String[] args) 
        throws InterruptedException {//懒汉模式的验证
        Thread t1 = new Thread(()->{
            try {
                System.out.println(
                    SingletonD.getInstance().hashCode()
                );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread t2 = new Thread(()->{
            try {
                System.out.println(
                    SingletonD.getInstance().hashCode()
                );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread t3 = new Thread(()->{
            try {
                System.out.println(
                    SingletonD.getInstance().hashCode()
                );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果:

这里写图片描述

上面的运行结果表明:当前的单例模式的实现在多线程的情况下是正确的。但是,这里使用了synchronized关键字实现同步方法,其实往往很多时候我们要同步的代码只有那么关键的几句,如果将整个方法都进行同步会降低程序的执行效率。因此不建议这样使用。

(2).相对高效的懒汉模式(细颗粒度的synchronized)

package csdn.qiang;

import java.util.concurrent.TimeUnit;
/**
 * 相对高效且线程安全的懒汉模式
 *           --双检查锁机制
 */
public class SingletonE {
    private static SingletonE singleton = null;
    private SingletonE(){}//私有化构造器防止外部创建对象
    public  static SingletonE getInstance() 
        throws InterruptedException{
        if(singleton==null){
            TimeUnit.SECONDS.sleep(3);
            synchronized(SingletonE.class){
                //使用synchronized锁定SingletonD.class
                if(singleton==null){
                    singleton = new SingletonE();
                }
            }
        }
        return singleton;
    }
    public static void main(String[] args) throws InterruptedException {//懒汉模式的验证
        Thread t1 = new Thread(()->{
            try {
                System.out.println(
                    SingletonE.getInstance().hashCode()
                );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread t2 = new Thread(()->{
            try {
                System.out.println(
                    SingletonE.getInstance().hashCode()
                );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread t3 = new Thread(()->{
            try {
                System.out.println(
                    SingletonE.getInstance().hashCode()
                );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果:

这里写图片描述

上面的运行结果表明:当前的单例模式的实现在多线程的情况下是正确的。这里使用了synchronized关键字实现同步代码块,并且使用了双检查机制。这样使用的优点是可以使既能保证核心代码的同步执行,又能获得相对较高的执行效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值