一、前言
单例模式应该是我们日常工作中用的或者见过最多的设计模式之一了,尤其是在设计各种框架的管理器时,基本都会针对不同种类的管理单元设计一个单例供业务代码使用。那么单例模式有多少种写法呢?每种方法适合用在什么场景下,分别有哪些优劣势呢?接下来我们就一起来看看吧。
二、主体
2.1、饿汉式单例模式-1-静态常量(推荐使用)
package com.le.test.designpattern.creational.singleton;
/**
* 饿汉式单例模式-1-静态常量(推荐使用)
* 优势:逻辑简单、线程安全
* 劣势:可能存在资源浪费的嫌疑(但如果你不需要这个类,为什么要加载它呢?所以一般情况下推荐这种写法)
* @author Le
*
*/
public class Singleton01 {
// 定义静态常量,类加载时实例化该示例,JVM可以保证类加载时是线程安全的,所以不需要我们单独加锁
public static Singleton01 INSTANCE = new Singleton01();
// 私有化默认构造函数,不允许外部独立创建对象,以实现单例
private Singleton01 () {}
// 对外开放的获取单例对象方法
public static Singleton01 getInstance() {
// 返回单例对象
return INSTANCE;
}
public static void main(String[] args) {
// 启动多线程并发获取对象,验证是否为单例模式(正式环境请使用线程池,哪怕一个线程)
for (int i = 0; i < 100; i ++) {
new Thread(new Runnable() {
public void run() {
// 逐个打印对象哈希码,不同哈希码代表不同对象
System.out.println(Singleton01.getInstance().hashCode());
}
}).start();
}
}
}
2.2、饿汉式单例模式-2-静态代码块(不推荐使用-跟静态常量没啥区别,见仁见智)
package com.le.test.designpattern.creational.singleton;
/**
* 饿汉式单例模式-2-静态代码块(不推荐使用-跟静态常量没啥区别,见仁见智)
* 优势:逻辑简单、线程安全
* 劣势:可能存在资源浪费的嫌疑(但如果你不需要这个类,为什么要加载它呢?所以一般情况下推荐这种写法)
* @author Le
*
*/
public class Singleton02 {
// 定义静态常量,类加载时实例化该示例,JVM可以保证类加载时是线程安全的,所以不需要我们单独加锁
public static Singleton02 INSTANCE;
// 使用静态代码块实例化单例对象,跟静态常量区别不大
static {INSTANCE = new Singleton02();}
// 私有化默认构造函数,不允许外部独立创建对象,以实现单例
private Singleton02 () {}
// 对外开放的获取单例对象方法
public static Singleton02 getInstance() {
// 返回单例对象
return INSTANCE;
}
public static void main(String[] args) {
// 启动多线程并发获取对象,验证是否为单例模式(正式环境请使用线程池,哪怕一个线程)
for (int i = 0; i < 100; i ++) {
new Thread(new Runnable() {
public void run() {
// 逐个打印对象哈希码,不同哈希码代表不同对象
System.out.println(Singleton02.getInstance().hashCode());
}
}).start();
}
}
}
2.3、懒汉式单例模式-1-线程不安全(不要用)
package com.le.test.designpattern.creational.singleton;
/**
*懒汉式单例模式-1-线程不安全(不要用)
* 优势:实现了懒加载,仅在需要时构建需要的对象
* 劣势:线程不安全,无法保证单例
* @author Le
*
*/
public class Singleton03 {
// 定义静态常量
public static Singleton03 INSTANCE;
// 私有化默认构造函数,不允许外部独立创建对象,以实现单例
private Singleton03 () {}
// 对外开放的获取单例对象方法
public static Singleton03 getInstance() {
// 获取实例前先判空,若不为空直接返回已构建的对象
if (INSTANCE == null) {
// 高并发下可能会出现构建多个实例的情况
try {Thread.sleep(1);} catch (InterruptedException e) {}
// 若进入方法时实例为空则实例化一个
INSTANCE = new Singleton03();
}
// 返回单例对象
return INSTANCE;
}
public static void main(String[] args) {
// 启动多线程并发获取对象,验证是否为单例模式(正式环境请使用线程池,哪怕一个线程)
for (int i = 0; i < 100; i ++) {
new Thread(new Runnable() {
public void run() {
// 逐个打印对象哈希码,不同哈希码代表不同对象
System.out.println(Singleton03.getInstance().hashCode());
}
}).start();
}
}
}
2.4、懒汉式单例模式-2-线程安全-同步方法(不推荐使用)
package com.le.test.designpattern.creational.singleton;
/**
*懒汉式单例模式-2-线程安全-同步方法(不推荐使用)
* 优势:实现了懒加载,且线程安全
* 劣势:高并发下会频发出现锁竞争
* @author Le
*
*/
public class Singleton04 {
// 定义静态常量
public static Singleton04 INSTANCE;
// 私有化默认构造函数,不允许外部独立创建对象,以实现单例
private Singleton04 () {}
// 对外开放的获取单例对象方法(使用synchronized关键字修饰static方法,会在整个类对象上加锁)
public static synchronized Singleton04 getInstance() {
// 获取实例前先判空,若不为空直接返回已构建的对象
if (INSTANCE == null) {
// 哪怕高并发依然可以实现单例
try {Thread.sleep(1);} catch (InterruptedException e) {}
// 若进入方法时实例为空则实例化一个
INSTANCE = new Singleton04();
}
// 返回单例对象
return INSTANCE;
}
public static void main(String[] args) {
// 启动多线程并发获取对象,验证是否为单例模式(正式环境请使用线程池,哪怕一个线程)
for (int i = 0; i < 100; i ++) {
new Thread(new Runnable() {
public void run() {
// 逐个打印对象哈希码,不同哈希码代表不同对象
System.out.println(Singleton04.getInstance().hashCode());
}
}).start();
}
}
}
2.5、懒汉式单例模式-3-线程不安全-同步代码块(不要用)
package com.le.test.designpattern.creational.singleton;
/**
*懒汉式单例模式-3-线程不安全-同步代码块(不要用)
* 优势:实现了懒加载,仅在需要时构建需要的对象
* 劣势:线程不安全,无法保证单例
* @author Le
*
*/
public class Singleton05 {
// 定义静态常量
public static Singleton05 INSTANCE;
// 私有化默认构造函数,不允许外部独立创建对象,以实现单例
private Singleton05 () {}
// 对外开放的获取单例对象方法
public static Singleton05 getInstance() {
// 获取实例前先判空,若不为空直接返回已构建的对象
if (INSTANCE == null) {
// 实例化对象前先加锁(但可能同时有多个线程进入抢锁代码位置,依然可能出现多次构建)
synchronized (Singleton05.class) {
// 高并发下可能会出现构建多个实例的情况
try {Thread.sleep(1);} catch (InterruptedException e) {}
// 若进入方法时实例为空则实例化一个
INSTANCE = new Singleton05();
}
}
// 返回单例对象
return INSTANCE;
}
public static void main(String[] args) {
// 启动多线程并发获取对象,验证是否为单例模式(正式环境请使用线程池,哪怕一个线程)
for (int i = 0; i < 100; i ++) {
new Thread(new Runnable() {
public void run() {
// 逐个打印对象哈希码,不同哈希码代表不同对象
System.out.println(Singleton05.getInstance().hashCode());
}
}).start();
}
}
}
2.6、懒汉式单例模式-4-线程安全-双重检查(不推荐)
package com.le.test.designpattern.creational.singleton;
/**
*懒汉式单例模式-4-线程安全-双重检查(不推荐)
* 优势:实现了懒加载,且线程安全,高并发下除第一次实例化时可能出现锁竞争,之后不会再加锁
* 劣势:逻辑相对复杂
* @author Le
*
*/
public class Singleton06 {
// 定义静态常量(这个volatile必须写,否则可能会因为指令重排导致双重检查逻辑失效,依然可能出现多实例)
public static volatile Singleton06 INSTANCE;
// 私有化默认构造函数,不允许外部独立创建对象,以实现单例
private Singleton06 () {}
// 对外开放的获取单例对象方法(使用synchronized关键字修饰static方法,会在整个类对象上加锁)
public static synchronized Singleton06 getInstance() {
// 获取实例前先判空,若不为空直接返回已构建的对象
if (INSTANCE == null) {
// 实例化对象前先加锁
synchronized (Singleton06.class) {
// 为防止同时有多个线程进入抢锁代码位置,抢到锁的线程再次判断是否需要实例化
if (INSTANCE == null) {
// 哪怕高并发依然可以实现单例
try {Thread.sleep(1);} catch (InterruptedException e) {}
// 若进入方法时实例为空则实例化一个
INSTANCE = new Singleton06();
}
}
}
// 返回单例对象
return INSTANCE;
}
public static void main(String[] args) {
// 启动多线程并发获取对象,验证是否为单例模式(正式环境请使用线程池,哪怕一个线程)
for (int i = 0; i < 100; i ++) {
new Thread(new Runnable() {
public void run() {
// 逐个打印对象哈希码,不同哈希码代表不同对象
System.out.println(Singleton06.getInstance().hashCode());
}
}).start();
}
}
}
2.7、内部类单例模式-线程安全(推荐使用)
package com.le.test.designpattern.creational.singleton;
/**
*内部类单例模式-线程安全(推荐使用)
* 优势:实现了懒加载,且线程安全,逻辑简单且仅在需要时构建(加载外部类Singleton07时,内部类SingletonHolder不会被加载)
* 劣势:不能防止反序列化可能导致的单例失效
* @author Le
*
*/
public class Singleton07 {
// 私有化默认构造函数,不允许外部独立创建对象,以实现单例
private Singleton07 () {}
// 定义内部类管理单例对象
private static class SingletonHolder {
// 定义静态常量
private static final Singleton07 INSTANCE = new Singleton07();
}
// 对外开放的获取单例对象方法
public static Singleton07 getInstance() {
// 返回单例对象
return SingletonHolder.INSTANCE;
}
public static void main(String[] args) {
// 启动多线程并发获取对象,验证是否为单例模式(正式环境请使用线程池,哪怕一个线程)
for (int i = 0; i < 100; i ++) {
new Thread(new Runnable() {
public void run() {
// 逐个打印对象哈希码,不同哈希码代表不同对象
System.out.println(Singleton07.getInstance().hashCode());
}
}).start();
}
}
}
2.8、枚举单例模式-线程安全(相对推荐使用)
package com.le.test.designpattern.creational.singleton;
/**
* 枚举单例模式-线程安全(相对推荐使用)
* 优势:逻辑简单、线程安全且能保证不会被反序列化
* 劣势:大部分时候我们想要的单例是普通的类而不是枚举类
* @author Le
*
*/
public enum Singleton08 {
// 定义枚举属性,天生单例
INSTANCE;
// 可以随意书写我们单例对象所需提供的方法
public static void testMethod() {System.out.println("hello world!!!");}
public static void main(String[] args) {
Singleton08.testMethod();
// 启动多线程并发获取对象,验证是否为单例模式(正式环境请使用线程池,哪怕一个线程)
for (int i = 0; i < 100; i ++) {
new Thread(new Runnable() {
public void run() {
// 逐个打印对象哈希码,不同哈希码代表不同对象
System.out.println(Singleton08.INSTANCE.hashCode());
}
}).start();
}
}
}
三、结语
单例模式是相对比较简单的一个设计模式,不过在使用时还是有很多细节需要注意,否则一旦出现问题就是那种不好排查的问题,看完本文后你有没有学到新的知识呢?反正我是学到了