设计模式(三)——单例模式(7种代码模式 Java代码实现)

上一篇:设计模式(二)——抽象工厂模式

下一篇:设计模式(四)——原型模式

一、概述

官方解释:Ensure a class has only one instance, and provide a global point of access to it.(确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。)

我的理解:单例模式是实现类型最多的设计模式之一,本文给出七种实现模式,饿汉式、懒汉式、静态内部类、静态代码块、枚举方式和序列化情况下的单例模式、反射情况下的单例模式。

参与者:Singleton单例类

类图:

设计模式(三)——单例模式(7种代码模式 Java代码实现)_第1张图片

二、代码分析

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()方法

保护单例

反射不安全,可以用异常的方式保护单例

 

 

上一篇:设计模式(二)——抽象工厂模式

下一篇:设计模式(四)——原型模式

你可能感兴趣的:(#)