推荐:​​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​​​关键字,此时​​monitor​​​是​​Singleton.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容器进行管理。

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

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

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