【设计模式】单例模式

博主声明:

转载请在开头附加本文链接及作者信息,并标记为转载。本文由博主 威威喵 原创,请多支持与指教。

本文首发于此   博主威威喵  |  博客主页https://blog.csdn.net/smile_running

    今天,我们来学习设计模式中最简单一种模式之一就是 —— 单例模式(singleton pattern) 。那么何为单例模式呢?它主要在程序中起到什么作用?为什么要使用这种模式以及它的模板代码?带着前面几个问题,我们一一解剖这个所谓的单例模式。

单例模式(singleton pattern)

    单例,从命名的名词角度来解释,它的意思是单独的一个实例。没错,其实它就是这个意思。而在我们程序中,它也是仅仅存在的一个全局的实例,也就是实例化一次。它的创建只能通过自身来实例化,仅提供外部的一个唯一的访问自己的方法。

1、特点

  1.     仅有一个实例
  2.     自己实例化自己
  3.     提供一个唯一访问自身实例的方法

2、作用

    保证自身的唯一性,以确保数据的准确、唯一。在多线程情况下,多次访问数据对其进行操作则会造成数据紊乱,而使用单例则能确保数据是准确了。

3、实现方式

    单例模式的实现方式主要有4种,也是我们最经常写的几种,我们分别看它们的实现方式以及区别。

一、懒汉式(线程安全)

    优点:使用时才实例化,避免内存开销,可以做到随用随实例化。

    缺点:虽然保证了线程安全,但 synchronized 关键字会导致效率降低,因为每个线程进来时都必须枷锁。

/**
 * @Created by xww.
 * @Creation time 2018/8/14.
 */

public class Singleton {
    private static Singleton instance;

    public static synchronized Singleton getIns() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

二、饿汉式(线程安全)

    优点:没有 synchronized 枷锁,运行效率提升。

    缺点:在类加载时就被实例化,导致内存开销增大。

/**
 * @Created by xww.
 * @Creation time 2018/8/14.
 */

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

    public static Singleton getIns() {
        return instance;
    }
}

三、双重校验锁(线程安全)

    优点:使用双重校验锁机制,于懒汉模式有所优化在多线程中依然保持高效率,而且在使用时才实例化,综合以上两个优缺点于一身。这里对 instance 变量加不加 volatile 关键字是区别比较大的。

注意:

    如果不加 volatile 关键字,线程并不是绝对的安全,因为在 jdk1.5 版本后,声明变量与实例化是并发的,也就是说有可能变量声明后,实例化还未完成,导致变量的引用还是为空,就可能造成多个实例。volatile 也是在 jdk1.5 版本后出现的,可以解决这个问题。

/**
 * @Created by xww.
 * @Creation time 2018/8/14.
 */

public class Singleton {
    private volatile static Singleton instance;

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

四、静态内部类(线程安全)

    优点:在使用时才实例化,而且也是线程安全的,因为 new 只做了一次,综合以上两个优缺点于一身。与双重校验锁的方式相比,实现方式简单了点,代码也比较优雅,推荐使用!

/**
 * @Created by xww.
 * @Creation time 2018/8/14.
 */

public class Singleton {

    private static class Holder {
        private static final Singleton instance = new Singleton();
    }

    public static Singleton getIns() {
        return Holder.instance;
    }
}

五、枚举类(线程安全)

    枚举类也是线程安全的,它最简洁,是单例模式的最佳方式,支持序列化机制,防止被多次的实例化。而以上几种单利模式在序列化时都会出现多个实例的情况发生。

  • 几种单例模式的比较

  1. 一般建议使用饿汉式,不建议使用懒汉式。
  2. 如果在类装载过于耗时的处理时,使用静态内部类方式,要用才实例化,也可以用双重校验锁方式。
  3. 如果需要反序列化,用枚举类的方式。
  • 单例模式的运用 — 国王只有一位

    在多线程并发的情况下去获取一个国王类,可能就会导致国王被重复的创建,因为国王只有一位,所以要设置为单利,下面我们来看看单利模式如何应用吧,我就使用优雅的静态内部类来编写这个例子,其他几种方式也都一样,所以我就不重复的去写例子了,大家自己可以去尝试!

国王类:

package com.xww.dp.singleton;

/**
 * 静态内部类
 * @author xww
 *
 */
public class King {
	
	private King(){
		System.out.println("国王被创建了");
	}
	
	private static final class Holder {
		private static King instance = new King();
	}
	
	public static King getInstance() {
		return Holder.instance;
	}
}

 多线程去创建国王类,获取国王的实例:

package com.xww.dp.singleton;

/**
 * 23种设计模式 —— 单利模式
 * 
 * @author xww
 *
 */
public class SingletonPatternClient {

	public static void main(String[] args) {

		new Thread(new Runnable() {

			@Override
			public void run() {
				while (true) {
					King.getInstance();
					System.out.println(Thread.currentThread().getName());
					try {
						Thread.sleep(2000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}).start();

		new Thread(new Runnable() {

			@Override
			public void run() {
				while (true) {
					King.getInstance();
					System.out.println(Thread.currentThread().getName());
					try {
						Thread.sleep(3000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}

			}
		}).start();
	}

}

 打印日志如下:

    在第一次运行时,国王类会被创建,往后无论哪个线程再去调用 getInstance() 方法时,始终返回的国王类的实例只存在一个,这就是单利模式的应用。

看看源码中的单利模式 —— AsyncTask

    AsyncTask 是 Android SDK 中提供的一个异步处理操作,想要了解的可以看看我这篇写的对 AsyncTask 源码分析,在源码中初始化线程池操作就是一个典型的单利模式,代码如下:

    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    // We want at least 2 threads and at most 4 threads in the core pool,
    // preferring to have 1 less than the CPU count to avoid saturating
    // the CPU with background work
    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final int KEEP_ALIVE_SECONDS = 30;

    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };

    private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);

    /**
     * An {@link Executor} that can be used to execute tasks in parallel.
     */
    public static final Executor THREAD_POOL_EXECUTOR;

    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

     上面的代码用到了是一个 static 修饰的代码块,而这部分只会被创建一次,所以我们的单利模式并不是只有前面所讲的那几种写法,只要符合单利模式的特点,就可以被称作单利模式,至于代码如何写,其实并不重要,规范法其实也就是让别人都我们的代码的时候,不至于看不懂。

    不仅如此,AsyncTask 还用到了我们之前所说的枚举类作为 Task 的状态标记,我们知道枚举就是一个单利,并且它还是线程安全的,看看源码如何写:

    /**
     * Indicates the current status of the task. Each status will be set only once
     * during the lifetime of a task.
     */
    public enum Status {
        /**
         * Indicates that the task has not been executed yet.
         */
        PENDING,
        /**
         * Indicates that the task is running.
         */
        RUNNING,
        /**
         * Indicates that {@link AsyncTask#onPostExecute} has finished.
         */
        FINISHED,
    }

使用时:

    private volatile Status mStatus = Status.PENDING;

        mStatus = Status.RUNNING;
        mStatus = Status.FINISHED;

     好了,到此为止,我们从单利模式的介绍及作用开始,并书写的几种常见的单利模式以及它们的优缺点,还写了一个单利模式的例子,验证在多线程并发时,单利模式永远只有一个实例,最后还看了 AsyncTask 的源码里面是如何使用单利模式的,单利模式其实就是这么简单。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值