设计模式-单例模式(Singleton Pattern)

推荐:Java设计模式汇总

单例模式

定义
确保一个类只有一个实例,并为整个系统提供一个全局访问点 (向整个系统提供这个实例)。

类型
创建型。

例子
例子将展示饿汉式懒汉式双重检查锁式静态内部类式枚举单例式创建单例模式。

饿汉式
饿汉式,顾名思义,单例模式类迫不及待的想要创建实例了(因为饿了),有两种实现方式。

1.私有静态变量(线程安全)

Singleton类,单例模式类,在类加载时便会创建一个私有静态变量instance,也就是该类的实例,再通过公共接口getInstance()来发布该实例,这里使用原子整型实例来记录创建单例模式类实例的次数。

package com.kaven.design.pattern.creational.singleton;

import java.util.concurrent.atomic.AtomicInteger;

public class Singleton {
    private static AtomicInteger count = new AtomicInteger();
    private static Singleton instance = new Singleton();

    private Singleton(){
        count.getAndAdd(1);
    }

    public static Singleton getInstance(){
        return instance;
    }

    public static AtomicInteger getCount() {
        return count;
    }
}

测试:

用单线程进行测试,创建单例模式类实例10000000次,结果没问题。

package com.kaven.design.pattern.creational.singleton;

public class SingleTest {
    public static void main(String[] args) {

        for (int i = 0; i < 10000000; i++) {
            Singleton singleton = Singleton.getInstance();
        }
        System.out.println("创建了 "+Singleton.getCount()+" 次单例模式类实例");
    }
}

结果:

创建了 1 次单例模式类实例

再用多线程进行测试,MyRunnable类实现了Runnable接口,定义了线程的任务,就是创建Singleton类的实例。

package com.kaven.design.pattern.creational.singleton;

public class MyRunnable implements Runnable {

    public void run() {
        Singleton singleton= Singleton.getInstance();
    }
}

main线程中运行了10个线程,等10个线程全部执行完成后再输出结果.

package com.kaven.design.pattern.creational.singleton;

public class Test {
    private static final int THREAD_NUMBER = 10;

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[THREAD_NUMBER];
        MyRunnable myRunnable = new MyRunnable();
        for (int i = 0; i < THREAD_NUMBER; i++) {
            threads[i] = new Thread(myRunnable);
            threads[i].setName("线程"+i);
            threads[i].start();
        }
        for (int i = 0; i < THREAD_NUMBER; i++) {
            threads[i].join();
        }
        System.out.println("线程全部执行完成!");
        System.out.println("创建了 "+Singleton.getCount()+" 次单例模式类实例");
    }
}

结果:

线程全部执行完成!
创建了 1 次单例模式类实例

2.静态块(线程安全)
使用一个静态块来创建Singleton类实例,和上面的例子一样,静态块在类加载时会被执行,也就创建了Singleton类实例。

package com.kaven.design.pattern.creational.singleton;

import java.util.concurrent.atomic.AtomicInteger;

public class Singleton {
    private static AtomicInteger count = new AtomicInteger();
    private static Singleton instance ;

    static {
        instance = new Singleton();
    }

    private Singleton(){
        count.getAndAdd(1);
    }

    public static Singleton getInstance(){
        return instance;
    }

    public static AtomicInteger getCount() {
        return count;
    }
}

可以使用上面的两种测试方法进行测试,结果是一样的,这里就不重复了。

懒汉式
懒汉式,在需要单例模式类实例时它才创建出来给你(因为很懒)。

1.单重检查(线程不安全)

package com.kaven.design.pattern.creational.singleton;

import java.util.concurrent.atomic.AtomicInteger;

public class Singleton {
    private static AtomicInteger count = new AtomicInteger();
    private static Singleton instance ;

    private Singleton(){
        count.getAndAdd(1);
    }

    public static Singleton getInstance(){

        if(instance == null){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("出错啦!");
            }
            instance = new Singleton();
        }
        return instance;
    }

    public static AtomicInteger getCount() {
        return count;
    }
}

单线程测试是没问题的。
多线程测试出现了问题,创建了10个不同实例,如下所示:

线程全部执行完成!
创建了 10 次单例模式类实例

为什么这种方式实现单例模式在多线程下会出现问题,这就涉及到多线程编程的内容了,这里就不细说了,可以自行百度。

2.同步方法中单重检查(线程安全)
在获取单例模式类实例的方法getInstance()上加上synchronized关键字,此时monitorSingleton.class,下面的两种实现方法差不多(这里的Thread.sleep(1000)是多余的,主要是为了与上面的方式进行对比)。

package com.kaven.design.pattern.creational.singleton;

import java.util.concurrent.atomic.AtomicInteger;

public class Singleton {
    private static AtomicInteger count = new AtomicInteger();
    private static Singleton instance ;

    private Singleton(){
        count.getAndAdd(1);
    }

    public synchronized static Singleton getInstance(){

        if(instance == null){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("出错啦!");
            }
            instance = new Singleton();
        }
        return instance;
    }

    public static AtomicInteger getCount() {
        return count;
    }
}
package com.kaven.design.pattern.creational.singleton;

import java.util.concurrent.atomic.AtomicInteger;

public class Singleton {
    private static AtomicInteger count = new AtomicInteger();
    private static Singleton instance ;

    private Singleton(){
        count.getAndAdd(1);
    }

    public static Singleton getInstance(){
        synchronized (Singleton.class){
            if(instance == null){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println("出错啦!");
                }
                instance = new Singleton();
            }
        }
        return instance;
    }

    public static AtomicInteger getCount() {
        return count;
    }
}

单线程测试和多线程测试都没问题。
但这种将方法同步或者将大部分代码进行同步会导致该方法的同步锁非常重量级(当业务非常复杂时),所以我们应该减少代码同步的范围,这里我们主要担心instance = new Singleton()会在多线程下被执行多次,这就违背了单例模式的初衷,所以将instance = new Singleton()代码进行同步,也就是下面的方法3。

3.单重检查锁(线程不安全)

package com.kaven.design.pattern.creational.singleton;

import java.util.concurrent.atomic.AtomicInteger;

public class Singleton {
    private static AtomicInteger count = new AtomicInteger();
    private static volatile Singleton instance ;

    private Singleton(){
        count.getAndAdd(1);
    }

    public static Singleton getInstance(){

        if(instance == null){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("出错啦!");
            }
            synchronized (Singleton.class){
                instance = new Singleton();
            }
        }
        return instance;
    }

    public static AtomicInteger getCount() {
        return count;
    }
}

单线程测试也是没问题的。
多线程测试也出现了问题,同样创建了10个不同实例,如下所示:

线程全部执行完成!
创建了 10 次单例模式类实例

为什么这种方式实现单例模式在多线程下会出现问题,这就涉及到多线程编程的内容了,这里就不细说了,可以自行百度。
为了解决这种问题,就需要双重检查锁式这种方法。

双重检查锁式(线程安全)
双重检查锁式也是懒汉式的一种,这里分开进行讨论。
volatile修饰instance,再使用双重检查锁,这种单例模式类创建实例是线程安全的。
为什么需要用volatile修饰instance
双重检查锁单例模式为什么要用volatile关键字?

package com.kaven.design.pattern.creational.singleton;

import java.util.concurrent.atomic.AtomicInteger;

public class Singleton {
    private static AtomicInteger count = new AtomicInteger();
    private static volatile Singleton instance ;

    private Singleton(){
        count.getAndAdd(1);
    }

    public static Singleton getInstance(){

        if(instance == null){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("出错啦!");
            }
            synchronized (Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    public static AtomicInteger getCount() {
        return count;
    }
}

无论是单线程测试还是多线程测试这里都是没有问题的,可以多测试几遍,思考为什么这样就没有问题了。

静态内部类式(线程安全)

package com.kaven.design.pattern.creational.singleton;

import java.util.concurrent.atomic.AtomicInteger;

public class Singleton {
    private static AtomicInteger count = new AtomicInteger();

    private Singleton(){
        count.getAndAdd(1);
    }

    public static Singleton getInstance(){
        return Inner.instance;
    }

    public static AtomicInteger getCount() {
        return count;
    }
    private static class Inner{
        private static final Singleton instance = new Singleton();
    }
}

单线程测试还是多线程测试这里都是没有问题的。

枚举单例式

package com.kaven.design.pattern.creational.singleton;

public enum Singleton {
    INSTANCE;

    //可以省略此方法,通过Singleton.INSTANCE进行操作
    public static Singleton getInstance(){
        return INSTANCE;
    }

}

这里不方便进行测试,或者说我不知道怎么测试,小伙伴们知道方法的话可以贴出来,感谢感谢。

适用场景

  • 网站访问量计数器。
  • 项目中用于读取配置文件的类。
  • Spring中,每个Bean默认都是单例的,这样便于Spring容器进行管理。

优点
由于单例模式只生成了一个实例,所以能够节约系统资源、减少性能开销、提高系统效率

缺点
不适合用于变化频繁的对象;如果实例化的对象长时间不被利用,系统会认为该对象是垃圾而被回收,这可能会导致对象状态的丢失;也正是因为系统中只有一个实例,这样就导致了单例类的职责过重可能在应用中会违背单一职责原则,同时也没有抽象类或者接口做底层设计,这样扩展起来有一定的困难

如果有说错的地方,请大家不吝赐教(记得留言哦~~~~)。

扩展阅读:
为什么要用枚举实现单例模式(避免反射、序列化问题)
序列化破坏单例模式以及如何防御的应用与Debug分析
反射破坏单例模式以及如何防御

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ITKaven

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

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

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

打赏作者

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

抵扣说明:

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

余额充值