文档章节

单例模式与多线程

umgsai
 umgsai
发布于 2016/09/13 20:54
字数 1549
阅读 9
收藏 0
立即加载/饿汉模式

立即加载就是使用类的时候已经将对象创建完毕。

public class MyObject {
	//立即加载方式==饿汉模式
	private static MyObject myObject = new MyObject();
	private MyObject(){
	}
	public static MyObject getInstance(){
		//立即加载
		//缺点是不能有其他实例变量
		//getInstance()方法没有同步,有可能出现非线程安全问题
		return myObject;
	}
}

public class MyThread extends Thread {
	@Override
	public void run() {
		System.out.println(MyObject.getInstance().hashCode());
	}
}

public class Main {
	public static void main(String[] args) {
		MyThread t1 = new MyThread();
		MyThread t2 = new MyThread();
		MyThread t3 = new MyThread();
		t1.start();
		t2.start();
		t3.start();
	}
}

运行程序,控制台打印结果如下:

1795478472
1795478472
1795478472

控制台打印的hashCode是同一个值,说明对象是同一个,也就实现了立即加载型单例模式。


延迟加载/懒汉模式

延迟加载就是在调用get()方法时实例才被创建

public class MyObject {
	private static MyObject myObject;
	private MyObject(){
	}
	public static MyObject getInstance() {
		//延迟加载
		if (myObject != null) {
			return myObject;
		}
		myObject = new MyObject();
		return myObject;
	}
}

public class MyThread extends Thread {
	@Override
	public void run() {
		System.out.println(MyObject.getInstance().hashCode());
	}
}

public class Main {
	public static void main(String[] args) {
		MyThread t1 = new MyThread();
		t1.start();
	}
}

程序运行结果如下:

1396452035

此实验虽然取得一个对象的实例,但是如果是在多线程环境中,就会出现取出多个实例的情况。对以上代码中的main函数做如下修改:

public class Main {
	public static void main(String[] args) {
		MyThread t1 = new MyThread();
		MyThread t2 = new MyThread();
		MyThread t3 = new MyThread();
		t1.start();
		t2.start();
		t3.start();
	}
}

重新运行程序,控制台打印结果如下:

166471260
1795478472
1858758426

控制台打印了三个不同的hashCode,说明并没有实现单例模式。


延迟加载/懒汉模式 解决方案
public class MyObject {
	private static MyObject myObject;
	private MyObject(){
	}
	//整个方法上锁,效率较低
	synchronized public static MyObject getInstance() {
		//延迟加载
		if (myObject != null) {
			return myObject;
		}
		try {
		//模拟一些耗时操作
				Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		myObject = new MyObject();
		return myObject;
	}
}

重新运行程序,控制台将打印出三个一样的hashCode。

以上方法对整个方法加锁,效率比较低。对以上代码做如下修改:

public class MyObject {
	private static MyObject myObject;
	private MyObject(){
	}
	public static MyObject getInstance() {
		//延迟加载
		if (myObject != null) {
			return myObject;
		}
		try {
			//模拟一个耗时操作
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		synchronized (MyObject.class) {
			myObject = new MyObject();
		}
		return myObject;
	}
}

重新运行程序,控制台打印结果如下:

774088025
641502649
1367113803

以上代码虽然解决的效率问题,但是仍然没有保证只创建一个实例。继续对以上代码做如下修改:

public class MyObject {
	private static MyObject myObject;
	private MyObject(){
	}
	public static MyObject getInstance() {
		//延迟加载
		if (myObject != null) {
			return myObject;
		}
		try {
			//模拟一个耗时操作
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		synchronized (MyObject.class) {
			if (myObject == null) {
				myObject = new MyObject();
			}
		}
		return myObject;
	}
}

重新运行程序,控制台输入结果如下:

641502649
641502649
641502649

使用双重检查锁(DCL)功能,成功解决了懒汉模式遇到的多线程问题。DCL也是大多数多线程结合单例模式使用的解决方案。


使用静态内置类实现单例模式
public class MyObject {
	private static class MyObjectHandler{
		private static MyObject myObject = new MyObject();
	}
	private MyObject(){
		
	}
	public static MyObject getInstance () {
		return MyObjectHandler.myObject;
	}
}

运行程序,控制台输出结果如下:

774088025
774088025
774088025

序列化与反序列化的单例模式实现
public class MyObject implements Serializable {
	private static final long serialVersionUID = 1L;
	private static class MyObjectHandler{
		private static MyObject myObject = new MyObject();
	}
	private MyObject(){
		
	}
	public static MyObject getInstance () {
		return MyObjectHandler.myObject;
	}
}

public class SaveAndRead {
	public static void main(String[] args) {
		try {//序列化对象到磁盘
			MyObject myObject = MyObject.getInstance();
			FileOutputStream fileOutputStream = new FileOutputStream(new File("/Users/shangyidong/Downloads/myObjectFile.txt"));
			ObjectOutputStream objectOutput = new ObjectOutputStream(fileOutputStream);
			objectOutput.writeObject(myObject);
			objectOutput.close();
			fileOutputStream.close();
			System.out.println(myObject.hashCode());
		} catch (Exception e) {
			e.printStackTrace();
		}
		/****************/
		try {//从磁盘反序列化到对象
			FileInputStream fileInputStream = new FileInputStream(new File("/Users/shangyidong/Downloads/myObjectFile.txt"));
			ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
			MyObject myObject = (MyObject) objectInputStream.readObject();
			objectInputStream.close();
			fileInputStream.close();
			System.out.println(myObject.hashCode());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

运行程序,控制台打印结果如下:

988487739
1878277481

控制台打印出的hashCode不同,存储到磁盘上的对象和从磁盘读取出来的对象并不是同一个对象。对以上代码做如下修改:

public class MyObject implements Serializable {
	private static final long serialVersionUID = 1L;
	private static class MyObjectHandler{
		private static MyObject myObject = new MyObject();
	}
	private MyObject(){
		
	}
	public static MyObject getInstance () {
		return MyObjectHandler.myObject;
	}
	protected Object readResolve(){
		System.out.println("readResolve invoked");
		return MyObjectHandler.myObject;
	}
}

重新运行程序,控制台打印结果如下:

1066557918
readResolve invoked
1066557918

反序列化时使用了ReadResolve()方法,存储到磁盘上的对象和从磁盘上取出来的对象是同一个对象。


使用static代码块实现单例模式

静态代码块中的代码在使用类的时候就已经执行了,所以可以应用静态代码块的这个特性来实现单例模式。

public class MyObject {
	private static MyObject myObject = null;
	private MyObject(){
		
	}
	static{
		myObject = new MyObject();
	}
	public static MyObject getInstance() {
		return myObject;
	}
}

public class MyThread extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 3; i++) {
			System.out.println(MyObject.getInstance().hashCode());
		}
	}
}

public class Main {
	public static void main(String[] args) {
		MyThread t1 = new MyThread();
		MyThread t2 = new MyThread();
		MyThread t3 = new MyThread();
		t1.start();
		t2.start();
		t3.start();
	}
}

运行程序,控制台输出结果如下:

708252873
708252873
708252873
708252873
708252873
708252873
708252873
708252873
708252873

使用enum枚举数据类型实现单例模式

枚举enum和静态代码块的特性相似,在使用枚举类时,构造方法会被自动调用,也可以实现单例设计模式。

public enum MyObject {
	connectionFactory;
	private Connection connection;
	private MyObject(){
		try {
			System.out.println("创建MyObject对象");
			String url = "jdbc:mysql://127.0.0.1:3306/test";
			String user = "root"; 
	        String password = "";
	        String driver = "com.mysql.jdbc.Driver";
	        Class.forName(driver);
	        connection = DriverManager.getConnection(url, user, password);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public Connection getConnection() {
		return connection;
	}
}

public class MyThread extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 3; i++) {
			System.out.println(MyObject.connectionFactory.getConnection().hashCode());
		}
	}
}

public class Main {
	public static void main(String[] args) {
		MyThread t1 = new MyThread();
		MyThread t2 = new MyThread();
		MyThread t3 = new MyThread();
		t1.start();
		t2.start();
		t3.start();
	}
}

运行程序,控制台打印结果如下:

创建MyObject对象
1253195928
1253195928
1253195928
1253195928
1253195928
1253195928
1253195928
1253195928
1253195928

完善使用enum枚举实现单例模式

上面的代码中将枚举类进行了暴露,违反了“职责单一原则”。下面进行改善:

public class MyObject {
	public enum MyEnumSingleton{
		connectionFactory;
		private Connection connection;
		private MyEnumSingleton(){
			try {
				System.out.println("创建MyObject对象");
				String url = "jdbc:mysql://127.0.0.1:3306/test";
				String user = "root"; 
		        String password = "";
		        String driver = "com.mysql.jdbc.Driver";
		        Class.forName(driver);
		        connection = DriverManager.getConnection(url, user, password);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		public Connection getConnection() {
			return connection;
		}	
	}
	public static Connection getConnection() {
		return MyEnumSingleton.connectionFactory.getConnection();
	}
}

public class MyThread extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 3; i++) {
			System.out.println(MyObject.getConnection().hashCode());
		}
	}
}

public class Main {
	public static void main(String[] args) {
		MyThread t1 = new MyThread();
		MyThread t2 = new MyThread();
		MyThread t3 = new MyThread();
		t1.start();
		t2.start();
		t3.start();
	}
}

程序运行结果如下:

创建MyObject对象
2039988480
2039988480
2039988480
2039988480
2039988480
2039988480
2039988480
2039988480
2039988480

© 著作权归作者所有

umgsai
粉丝 0
博文 4
码字总数 8053
作品 0
黄冈
私信 提问
PHP设计模式(一):简介及创建型模式

我们分三篇文章来总结一下设计模式在PHP中的应用,这是第一篇创建型模式。 一、设计模式简介 首先我们来认识一下什么是设计模式: 设计模式是一套被反复使用、容易被他人理解的、可靠的代码设...

juhenj
2014/05/15
228
2
创建型模式.单例模式-懒汉、饿汉、枚举、原子

1 安全发布对象的四种方法 在多线程中,为了保证线程安全性,我们要正确地发布对象,保证发布地对象不要逸出。 在静态初始化函数中初始化一个对象引用 将对象的引用保存到volatile类型域或者...

阿杜杜不是阿木木
2018/03/28
0
0
【23种设计模式之一】单例设计模式(翻译)

引言: 这一系列文章,翻译自网络上的文章,不过中间会夹杂着个人的理解,非原创,不过中文应该算是原创。 下面介绍,使用设计模式的一些好处: 1、设计模式是已经在工业生产中使用的,用于解...

敲代码猥琐男
2015/01/13
0
2
Python学习:19.Python设计模式-单例模式

一、单例模式存在的意义   在这里的单例就是只有一个实例(这里的实例就像在面向对象的时候,创建了一个对象也可以说创建了一个实例),只用一个实例进行程序设计,首先我们可以了解一下什...

BD-ld-2017
2018/07/31
0
0
【设计模式笔记】(十六)- 代理模式

一、简述 代理模式(Proxy Pattern),为其他对象提供一个代理,并由代理对象控制原有对象的引用;也称为委托模式。 其实代理模式无论是在日常开发还是设计模式中,基本随处可见,中介者模式中...

MrTrying
2018/06/24
0
0

没有更多内容

加载失败,请刷新页面

加载更多

将博客搬至CSDN

https://blog.csdn.net/qq_38157006

Marhal
14分钟前
1
0
unicode Java中求字符串长度length()和codePointCount()的区别

在计算字符串长度时,Java的两种方法length()和codePointCount()一度让我困惑,运行书上例子得到的长度值是相等的,那为什么要设定两个方法呢? 对于普通字符串,这两种方法得到的值是一样的...

泉天下
14分钟前
2
0
uin-app 一、学习理由

选择uni-app 理由 别人的理由 1. 5+ 有HTML5+和Native.js技术,HTML5+包含常用的跨平台的几百个API,能满足常规