在标准的23个设计模式中,单例设计模式在应用中是比较常见的。但在常规的该模式教学资料介绍中,多数没有结合多线程技术作为参考,这就造成在使用多线程技术的单例模式时会出现一些意想不到的情况,这样的代码如果在生产环境中出现异常,有可能造成灾难性的后果。
在学习多线程技术的单例模式时,只需要考虑一件事,那就是:如何使单例模式遇到多线程是安全的、正确的。下面就不同方式实现多线程中的单例进行介绍:
一、“饿汉模式”实现多线程中的单例
下面通过示例演示:
package com.javapatterns.singleton;
/**
* 测试饿汉模式在多线程环境下的单例
*/
public class Test {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
Thread t2 = new Thread(myRunnable);
Thread t3 = new Thread(myRunnable);
t1.start();
t2.start();
t3.start();
}
}
class Admin {
// 提供一个当前类型的静态变量,静态变量在类加载时创建,而且只执行一次
private static Admin a = new Admin();
// 构造方法私有化
private Admin() {
}
/*
* 饿汉模式的缺点是不能有其他的实例变量 因为getInstance()方法没有同步,所以有可能会出现非线程安全问题,
* 解决方法是在getInstance()方法前加关键字synchronized使其同步
*/
// 提供一个公开的静态的获取当前类型对象的方法
synchronized public static Admin getInstance() {
return a;
}
}
/*
* 通过实现Runnable接口实现多线程
*/
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Admin.getInstance().hashCode());
}
}
运行结果如图 3-1所示:
图 3-1 “饿汉模式”结合多线程的运行结果
由图可知,控制台打印的hasCode是同一个值,说明对象是同一个,也就说明用“饿汉模式”实现了多线程中的单例。
二、“懒汉模式”实现多线程中的单例
“懒汉模式”实现多线程中的单例有多种方式:
- 声明synchronized关键字实现
- 通过同步代码块实现
- 使用DCL双检查锁机制实现
由于前两种方法的实现运行效率较低,所以只给出关键代码,不做详细介绍,下面会针对第三种实现方法展开介绍。
前两种实现方法的关键代码:
/*
* 第一种方法
* 通过声明synchronized关键字实现
* 要领:在getInstance()方法前加关键字synchronized
*/
synchronized public static Singleton getInstance() {
if (s == null) {
s = new Singleton();
}
return s;
}
/* * 第二种方法
* 通过同步代码块实现
* 要领:在getInstance()方法里的代码上对对应的Class类进行同步
* 注意:同步synchronized(class)代码块的作用其实和synchronized static方法的作用一样。
*
*/
public static Singleton getInstance() {
synchronized(Singleton.class) {
if (s == null) {
s = new Singleton();
}
return s;
}
}
通过使用DCL双检查锁机制实现的示例如下:
package com.javapatterns.singleton;
/**
* 测试DCL双检查锁机制在多线程环境下的单例
*/
public class Test {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
Thread t2 = new Thread(myRunnable);
Thread t3 = new Thread(myRunnable);
t1.start();
t2.start();
t3.start();
}
}
class Singleton {
// 提供一个当前类型的静态变量
private static Singleton s;
// 构造方法私有化
private Singleton() {
}
// 提供一个公开的静态的获取当前类型对象的方法
/*
* 使用双检测机制来解决问题,既保证了不需要同步代码的异步执行 又保证了单例的效果
*
*/
public static Singleton getInstance() {
try {
// 第一次检查
if (s != null) {
} else {
// 模拟在创建对象之前做一些准备性工作
Thread.sleep(3000);
// 通过同步synchronized(class)代码块对对应的类进行加锁
synchronized (Singleton.class) {
// 第二次检查
if (s == null) {
s = new Singleton();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return s;
}
}
/*
* 通过实现Runnable接口实现多线程
*/
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Admin.getInstance().hashCode());
}
}
运行结果如图 3-2所示
图 3-2 “懒汉模式”结合多线程的运行结果
由图可知,控制台打印的hasCode是同一个值,说明对象是同一个,也就说明用“懒汉模式”实现了多线程中的单例。
使用双检查锁功能,成功地解决了“懒汉模式”遇到多线程的问题。DCL也是大多数多线程结合单例模式使用的解决方案。
三、使用静态内部类实现多线程中的单例
下面通过示例演示:
package com.javapatterns.singleton;
/**
* 测试使用静态内部类实现在多线程环境下的单例
*/
public class Test {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
Thread t2 = new Thread(myRunnable);
Thread t3 = new Thread(myRunnable);
t1.start();
t2.start();
t3.start();
}
}
class Singleton {
//静态内部类
private static class InnerClass {
// 提供一个当前类型的静态变量,静态变量在类加载时创建,而且只执行一次
private static Singleton s = new Singleton();
}
// 构造方法私有化
private Singleton() {
}
// 提供一个公开的静态的获取当前类型对象的方法
public static Singleton getInstance() {
// 返回内部类里的当前类型的静态变量
return InnerClass.s;
}
}
/*
* 通过实现Runnable接口实现多线程
*/
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Admin.getInstance().hashCode());
}
}
运行结果如图 3-3所示
图 3-3 静态内部类实现的单例模式结合多线程的运行结果
由图可知,控制台打印的hasCode是同一个值,说明对象是同一个,也就说明用 静态内部类实现的单例模式实现了多线程中的单例。
四、使用static代码块实现多线程中的单例
静态代码块中的代码在使用类的时候就已经执行了,所以可以应用静态代码块的这个特性来实现多线程中的单例。
下面通过示例演示:
package com.javapatterns.singleton;
/**
* 测试static代码块实现的单例模式在多线程环境下的单例
*/
public class Test {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
Thread t2 = new Thread(myRunnable);
Thread t3 = new Thread(myRunnable);
t1.start();
t2.start();
t3.start();
}
}
class Singleton {
// 提供一个当前类型的静态变量
private static Singleton s;
// 构造方法私有化
private Singleton() {
}
// 在静态代码块中创建该类型的对象,因为在静态代码块中,所以只被执行一次,而且是在类被载入时执行
static {
s = new Singleton();
}
// 提供一个公开的静态的获取当前类型对象的方法
public static Singleton getInstance() {
return s;
}
}
/*
* 通过实现Runnable接口实现多线程
*/
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Admin.getInstance().hashCode());
}
}
未完,待续…