上一篇:设计模式(二)——抽象工厂模式
下一篇:设计模式(四)——原型模式
一、概述
官方解释:Ensure a class has only one instance, and provide a global point of access to it.(确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。)
我的理解:单例模式是实现类型最多的设计模式之一,本文给出七种实现模式,饿汉式、懒汉式、静态内部类、静态代码块、枚举方式和序列化情况下的单例模式、反射情况下的单例模式。
参与者:Singleton单例类
类图:
二、代码分析
2.1 饿汉式
第一步,私有化构造函数;
第二步,定义时初始化;
第三步,定义getInstance()方法返回对象实例;
第四步,新建线程类验证线程安全性(此步骤为验证该种单例实现方式线程安全性,可选)
public class SingleInstance {
public static void main(String[] args) {
MyThread _tMyThread=new MyThread();
MyThread _tMyThread2=new MyThread();
MyThread _tMyThread3=new MyThread();
_tMyThread.start();
_tMyThread2.start();
_tMyThread3.start();
}
}
class SingleObject{
private SingleObject(){ //第一步 私有化构造函数 禁止客户端用new新建对象
}
//第二步 提供新建对象入口 这里采用饿汉式
private static SingleObject _sSingleObject=new SingleObject();//饿汉式 定义时初始化 线程安全
// static 方法 保证客户端可以使用类名调用(即保证客户端在未新建对象时 可以调用此方法)
public static SingleObject getInstance(){
return _sSingleObject;
}
}
class MyThread extends Thread{ //线程类用来测试 线程安全性
@Override
public void run() {
System.out.println(SingleObject.getInstance().hashCode());
}
}
输出:
389269157
389269157
389269157
2.2懒汉式
第一步,私有化构造函数;
第二步,新建单例引用,并在构造函数中初始化;
第三步,定义getInstance()方法返回对象实例;
第四步,新建线程类验证线程安全性(此步骤为验证该种单例实现方式线程安全性,可选)。
2.2.1 懒汉式——线程不安全
public class SingleInstance {
public static void main(String[] args) {
for (int i=0;i<10;i++){ // 模拟10个线程
new MyThread().start();
}
}
}
class SingleObject{
private SingleObject(){ //第一步 私有化构造函数 禁止客户端用new新建对象
}
private static SingleObject _sSingleObject;
// static 方法 保证客户端可以使用类名调用(即保证客户端在未新建对象时 可以调用此方法)
public static SingleObject getInstance() throws Exception{
Thread.sleep(5000); //通过sleep() 等待5s 让线程不安全的问题暴露出来
if (_sSingleObject==null) {
_sSingleObject=new SingleObject();
}
return _sSingleObject;
}
}
class MyThread extends Thread{ //线程类用来测试 线程安全性
@Override
public void run() {
try {
System.out.println(SingleObject.getInstance().hashCode());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
输出:
193202248
412303685
193202248
193202248
193202248
389269157
193202248
1435984462
193202248
193202248
2.2.2 懒汉式 synchronize同步方法保证线程安全
public class SingleInstance {
public static void main(String[] args) {
for (int i=0;i<10;i++){ // 模拟10个线程
new MyThread().start();
}
}
}
class SingleObject{
private SingleObject(){ //第一步 私有化构造函数 禁止客户端用new新建对象
}
private static SingleObject _sSingleObject;
// static 方法 保证客户端可以使用类名调用(即保证客户端在未新建对象时 可以调用此方法)
public static synchronized SingleObject getInstance() throws Exception{
Thread.sleep(5000); //通过sleep() 等待5s 让线程不安全的问题暴露出来
if (_sSingleObject==null) {
_sSingleObject=new SingleObject();
}
return _sSingleObject;
}
}
class MyThread extends Thread{ //线程类用来测试 线程安全性
@Override
public void run() {
try {
System.out.println(SingleObject.getInstance().hashCode());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
输出:
968687297
968687297
968687297
968687297
968687297
968687297
968687297
968687297
968687297
968687297
2.2.3 懒汉式 synchronized同步代码块保证线程安全
public class SingleInstance {
public static void main(String[] args) {
for (int i=0;i<10;i++){ // 模拟10个线程
new MyThread().start();
}
}
}
class SingleObject{
private SingleObject(){ //第一步 私有化构造函数 禁止客户端用new新建对象
}
private static SingleObject _sSingleObject;
// static 方法 保证客户端可以使用类名调用(即保证客户端在未新建对象时 可以调用此方法)
public static SingleObject getInstance() throws Exception{
Thread.sleep(5000); //通过sleep() 等待5s 让线程不安全的问题暴露出来
synchronized (SingleObject.class) { //将需要原子化操作的代码放在同步代码块中,static方法的同步锁为类字节码,非static方法的同步锁为this,此处static方法
//同步代码块相对于同步方法 同步的内容少,效率高
if (_sSingleObject==null) {
_sSingleObject=new SingleObject();
}
}
return _sSingleObject;
}
}
class MyThread extends Thread{ //线程类用来测试 线程安全性
@Override
public void run() {
try {
System.out.println(SingleObject.getInstance().hashCode());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
输出:
774856018
774856018
774856018
774856018
774856018
774856018
774856018
774856018
774856018
774856018
2.2.4 懒汉式 lock机制保证线程安全
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SingleInstance {
public static void main(String[] args) {
for (int i=0;i<10;i++){ // 模拟10个线程
new MyThread().start();
}
}
}
class SingleObject{
private SingleObject(){ //第一步 私有化构造函数 禁止客户端用new新建对象
}
private static Lock lock=new ReentrantLock();
private static SingleObject _sSingleObject;
// static 方法 保证客户端可以使用类名调用(即保证客户端在未新建对象时 可以调用此方法)
public static SingleObject getInstance() throws Exception{
Thread.sleep(5000); //通过sleep() 等待5s 让线程不安全的问题暴露出来
lock.lock();
try {
if (_sSingleObject==null) {
_sSingleObject=new SingleObject();
}
} finally {
lock.unlock();
}
return _sSingleObject;
}
}
class MyThread extends Thread{ //线程类用来测试 线程安全性
@Override
public void run() {
try {
System.out.println(SingleObject.getInstance().hashCode());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
输出:
774856018
774856018
774856018
774856018
774856018
774856018
774856018
774856018
774856018
774856018
2.3 用静态内部类实现单例模式 线程安全 此种方式和饿汉式类似,只是将定义和初始化单例变量放在了静态内部类中
第一步,私有化构造函数;
第二步,新建单例引用,并在静态内部类中初始化;
第三步,定义getInstance()方法返回对象实例;
第四步,新建线程类验证线程安全性(此步骤为验证该种单例实现方式线程安全性,可选)。
public class SingleInstance {
public static void main(String[] args) {
for (int i=0;i<10;i++){ // 模拟10个线程
new MyThread().start();
}
}
}
class SingleObject{
private SingleObject(){ //第一步 私有化构造函数 禁止客户端用new新建对象
}
private static class InnerClass{
private static SingleObject _sSingleObject=new SingleObject();
}
// static 方法 保证客户端可以使用类名调用(即保证客户端在未新建对象时 可以调用此方法)
public static SingleObject getInstance() throws Exception{
Thread.sleep(5000); //通过sleep() 等待5s 让线程不安全的问题暴露出来
return InnerClass._sSingleObject; //外部类中可以访问内部类成员
}
}
class MyThread extends Thread{ //线程类用来测试 线程安全性
@Override
public void run() {
try {
System.out.println(SingleObject.getInstance().hashCode());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
输出:
389269157
389269157
389269157
389269157
389269157
389269157
389269157
389269157
389269157
389269157
2.4 用静态代码块实现单例模式 线程安全 此种方式和饿汉式类似,只是将定义和初始化单例变量放在了静态代码块中
第一步,私有化构造函数;
第二步,新建单例引用,并在静态代码块中初始化;
第三步,定义getInstance()方法返回对象实例;
第四步,新建线程类验证线程安全性(此步骤为验证该种单例实现方式线程安全性,可选)。
public class SingleInstance {
public static void main(String[] args) {
for (int i=0;i<10;i++){ // 模拟10个线程
new MyThread().start();
}
}
}
class SingleObject{
private SingleObject(){ //第一步 私有化构造函数 禁止客户端用new新建对象
}
private static SingleObject _sSingleObject;
static {
_sSingleObject=new SingleObject();
}
// static 方法 保证客户端可以使用类名调用(即保证客户端在未新建对象时 可以调用此方法)
public static SingleObject getInstance() throws Exception{
Thread.sleep(5000); //通过sleep() 等待5s 让线程不安全的问题暴露出来
return _sSingleObject; //外部类中可以访问内部类成员
}
}
class MyThread extends Thread{ //线程类用来测试 线程安全性
@Override
public void run() {
try {
System.out.println(SingleObject.getInstance().hashCode());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
输出:
1742654687
1742654687
1742654687
1742654687
1742654687
1742654687
1742654687
1742654687
1742654687
1742654687
2.5 用枚举的方式实现单例模式 线程安全
第一步,私有化构造函数;
第二步,新建单例引用,并在构造函数中初始化;
第三步,定义getInstance()方法返回对象实例;
第四步,新建线程类验证线程安全性(此步骤为验证该种单例实现方式线程安全性,可选)。
public class SingleInstance {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) { // 模拟10个线程
new MyThread().start();
}
}
}
enum SingleObject {
_singleFactory; // 枚举
SingleObject() {
_sSingleObject = new String("Single");
}
private String _sSingleObject;
public String getInstance() throws Exception {
Thread.sleep(5000); // 通过sleep() 等待5s 让线程不安全的问题暴露出来
return _sSingleObject;
}
}
class MyThread extends Thread { // 线程类用来测试 线程安全性
@Override
public void run() {
try {
System.out.println(SingleObject._singleFactory.getInstance().hashCode());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
输出:
-1818398616
-1818398616
-1818398616
-1818398616
-1818398616
-1818398616
-1818398616
-1818398616
-1818398616
-1818398616
2.6 序列化破坏单例模式及解决
第一步,私有化构造函数;
第二步,定义时初始化(这里使用饿汉式验证序列化对单例模式的破坏,读者可以选择其他单例实现方式验证 如懒汉式、静态内部类 静态代码块 枚举);
第三步,定义getInstance()方法返回对象实例;(饿汉式完成)
第四步,单例类implements Serializable并添加序列号serializableUid
第五步,客户端读写单例对象,打印出hashcode,体现序列化对单例模式的破坏;
第六步,单例类中readResolve()方法,保护单例模式不受序列化破坏。
2.6.1 序列化对单例模式的破坏
这里饿汉式为例(其他单例也可以 懒汉式、静态内部类、静态代码块、枚举),当单例模式实现序列化接口(implements Serializable),单例模式会受到破坏,如下:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SingleInstance {
public static void main(String[] args) {
// 写出到txt文件
try {
SingleObject _sSingleObject = SingleObject.getInstance();
FileOutputStream _oOutputStream = new FileOutputStream(new File("test.txt"));
ObjectOutputStream _oOutputStream2 = new ObjectOutputStream(_oOutputStream);
_oOutputStream2.writeObject(_sSingleObject);
_oOutputStream2.close();
_oOutputStream.close();
System.out.println(_sSingleObject.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
// 读入到程序中
try {
FileInputStream _iFileInputStream = new FileInputStream(new File("test.txt"));
ObjectInputStream _oInputStream = new ObjectInputStream(_iFileInputStream);
SingleObject _sReaderObject = (SingleObject) _oInputStream.readObject();
_oInputStream.close();
_iFileInputStream.close();
System.out.println(_sReaderObject.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
}
}
class SingleObject implements Serializable {
private static final long serializableUid = 1L;
private static final SingleObject _sSingleObject = new SingleObject();
private SingleObject() { // 私有化构造函数
}
public static SingleObject getInstance() {
return _sSingleObject;
}
}
输出:
1550089733
245257410
解决方法,加上一个readResolve()方法就好了
2.6.2 readResolve()方法保护单例模式不受序列化破坏
代码:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SingleInstance {
public static void main(String[] args) {
// 写出到txt文件
try {
SingleObject _sSingleObject = SingleObject.getInstance();
FileOutputStream _oOutputStream = new FileOutputStream(new File("test.txt"));
ObjectOutputStream _oOutputStream2 = new ObjectOutputStream(_oOutputStream);
_oOutputStream2.writeObject(_sSingleObject);
_oOutputStream2.close();
_oOutputStream.close();
System.out.println(_sSingleObject.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
// 读入到程序中
try {
FileInputStream _iFileInputStream = new FileInputStream(new File("test.txt"));
ObjectInputStream _oInputStream = new ObjectInputStream(_iFileInputStream);
SingleObject _sReaderObject = (SingleObject) _oInputStream.readObject();
_oInputStream.close();
_iFileInputStream.close();
System.out.println(_sReaderObject.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
}
}
class SingleObject implements Serializable {
private static final long serializableUid = 1L;
private static final SingleObject _sSingleObject = new SingleObject();
private SingleObject() { // 私有化构造函数
}
public static SingleObject getInstance() {
return _sSingleObject;
}
protected Object readResolve() {
return _sSingleObject;
}
}
输出:
1550089733
1550089733
2.7 反射破坏单例模式及解决
第一步,私有化构造函数;
第二步,定义时初始化(这里使用饿汉式验证反射对单例模式的破坏,读者可以选择其他单例实现方式验证 如懒汉式、静态内部类 静态代码块 枚举);
第三步,定义getInstance()方法返回对象实例;(饿汉式完成)
第四步,客户端使用反射新建对象,打印hashcode,体现反射对单例模式的破坏;
第五步,构造函数中检测,当实例不为空,抛出运行时异常,阻止反射新建对象,以异常的方式保护单例模式不受反射破坏。
代码:
public class SingleInstance {
public static void main(String[] args) throws Exception {
Class class1 = (Class) Class.forName("mypackage.SingleObject");
Constructor constructor = class1.getDeclaredConstructor(null);
constructor.setAccessible(true);
SingleObject s1 = constructor.newInstance();
SingleObject s2 = constructor.newInstance();
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
}
}
class SingleObject {
private static SingleObject _sSingleObject = new SingleObject();
private SingleObject() { // 私有化构造函数
}
public static SingleObject getInstance() {
return _sSingleObject;
}
}
输出:
366712642
1829164700
解决方法:单例类构造函数中检测,当实例对象不为空,抛出运行时异常,用异常的方式保护单例模式不受反射破坏。
代码:
输出:
public class SingleInstance {
public static void main(String[] args) throws Exception {
Class class1 = (Class) Class.forName("mypackage.SingleObject");
Constructor constructor = class1.getDeclaredConstructor(null);
constructor.setAccessible(true);
SingleObject s1 = constructor.newInstance();
SingleObject s2 = constructor.newInstance();
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
}
}
class SingleObject {
private static SingleObject _sSingleObject = new SingleObject();
private SingleObject() { // 私有化构造函数
if (_sSingleObject != null)
throw new RuntimeException();
}
public static SingleObject getInstance() {
return _sSingleObject;
}
}
输出:
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at mypackage.SingleInstance.main(SingleInstance.java:18)
Caused by: java.lang.RuntimeException
at mypackage.SingleObject.(SingleInstance.java:32)
... 5 more
三、小结
饿汉式 | 线程安全 | 序列化不安全,readResolve()方法保护单例 | 反射不安全,可以用异常的方式保护单例 |
懒汉式 | 线程不安全 (可使用静态内部类、静态代码块、lock机制实现线程安全) |
序列化不安全,readResolve()方法保护单例 | 反射不安全,可以用异常的方式保护单例 |
静态内部类 | 线程安全 | 序列化不安全,readResolve()方法保护单例 | 反射不安全,可以用异常的方式保护单例 |
静态代码块 | 线程安全 | 序列化不安全,readResolve()方法保护单例 | 反射不安全,可以用异常的方式保护单例 |
枚举方式 | 线程安全 | 序列化不安全,readResolve()方法 保护单例 |
反射不安全,可以用异常的方式保护单例 |
上一篇:设计模式(二)——抽象工厂模式
下一篇:设计模式(四)——原型模式