在使用多线程技术的单例模式时会出现一些意想不到的情况,这样的代码如果在生产环境中出现异常,有可能造成灾难性的后果。

  一、立即加载/“饿汉模式”

  立即加载就是使用类的时候已经将对象创建完毕,常见的实现方法就是直接new实例化。立即加载/“饿汉模式”是在调用方法前,实例已经被创建了。

  示例:从输出结果可以看出,虽然3个线程同时执行了同一个对象的同一个方法,但是输出的hashCode值是一样的,说明对象是同一个,因为private static MyObject myObject = new MyObject();所以也就实现了立即加载型单例设计模式。

package test;

public class MyObject {

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

import test.MyObject;

public class MyThread extends Thread {

    @Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
} }
package test.run;

import extthread.MyThread;

public class Run {

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

  二、延迟加载/“懒汉模式”

  延迟加载就是在调用get()方法时实例才被创建,常见的办法就是在get()方法中进行new实例化。

  (1)延迟加载/“懒汉模式”实现

  示例:由于只有一个线程,所以从结果来看确实取得了一个对象的实例,但是在多线程环境中,就会取出多个实例的情况,这与单例模式的初衷是相违背的。

package test;

public class MyObject {

    private static MyObject myObject;

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

import test.MyObject;

public class MyThread extends Thread {

    @Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
} }
package test.run;

import extthread.MyThread;

public class Run {

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

  (2)延迟加载/“懒汉模式”缺点

  示例:验证多线程中延迟加载的错误性。从输出可以看出来,3个线程创建了3个不同的实例,这是因为3个线程没有实现同步,即一开始都判断没有实例,所以都创建了实例。

package test;

public class MyObject {

    private static MyObject myObject;

    private MyObject() {
} public static MyObject getInstance() {
try {
if (myObject != null) {
} else {
//模拟在创建对象前做一些准备工作
Thread.sleep(3000);
myObject = new MyObject();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
} }
package extthread;

import test.MyObject;

public class MyThread extends Thread {

    @Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
} }
package test.run;

import extthread.MyThread;

public class Run {

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

  (3)延迟加载/“懒汉模式”解决方案

  ①声明synchronized关键字

  示例:修改getInstance()方法,加上synchronized关键字,既然多个线程可以同时进入getInstance()方法,那么只需要对getInstance()方法声明synchronized关键字即可,这样就可以得到相同实例的对象,但此种方法的运行效率非常低下,是同步运行的,下一个线程想要取得对象,就必须等待上一个线程释放锁之后,才可以继续执行。执行完需要花费9秒的时间。

package test;

public class MyObject {

    private static MyObject myObject;

    private MyObject() {
} synchronized public static MyObject getInstance() {
try {
if (myObject != null) {
} else {
//模拟在创建对象前做一些准备工作
Thread.sleep(3000);
myObject = new MyObject();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
} }
379353320
379353320
379353320

  ②尝试同步代码块

  示例:之前的加synchronized关键字是对整个方法上锁,这里虽然使用了同步代码块,但是几乎是整个方法里面的代码,虽然可以达到效果,但这对运行效率来说也是不利的。执行总时间也是9秒。

package test;

public class MyObject {

    private static MyObject myObject;

    private MyObject() {
} public static MyObject getInstance() {
try {
// 这种方法等同于
// synchronized public static MyObject getInstance()
// 效率一样很低,全部代码被上锁。
synchronized (MyObject.class) {
if (myObject != null) {
} else {
Thread.sleep(3000); myObject = new MyObject();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
} }
621856954
621856954
621856954

  ③针对某些重要的代码进行单独的同步

  示例:同步代码块可以针对某些重要的代码进行单独的同步,而其他的代码则不需要同步。这样在运行时,效率完全可以得到大幅提升。虽然不对sleep(3000)上锁这个思路很对,但是还是无法解决问题啊,3个线程还是分别都创建了一个实例。

package test;

public class MyObject {

    private static MyObject myObject;

    private MyObject() {
} public static MyObject getInstance() {
try {
if (myObject != null) {
} else {
// 模拟在创建对象之前做一些准备性的工作
Thread.sleep(3000);
//使用synchronized (MyObject.class)
//虽然部分代码被上锁
//但是还是有非线程安全问题
synchronized (MyObject.class) {
myObject = new MyObject();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
} }
180612070
621856954
686587801

  ④使用DCL双检查锁机制

  示例:采用双重检查锁机制,成功解决了延迟加载遇到多线程的问题。DCL也是大多数多线程结合单例模式使用的解决方案。整个代码执行时间远少于9秒。

package test;

public class MyObject {

    private volatile static MyObject myObject;

    private MyObject() {
} //使用双监测机制来解决问题,既保证了不需要同步代码的异步执行性,有保证了单例的效果
public static MyObject getInstance() {
try {
if (myObject != null) {
} else {
//模拟在创建对象之前做的一些准备性的工作
Thread.sleep(3000);
synchronized (MyObject.class) {
if (myObject == null) {
myObject = new MyObject();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
//称为Double-Check Locking
}
686587801
686587801
686587801

  三、使用静态内置类实现单例模式

  除了DCL外,其他的方法也能达到同样的效果。

  示例:通过使用静态内之类的方式也可以实现3个线程只创建一个对象的需求。

package test;

public class MyObject {

    //内部静态类方式
private static class MyObjectHandler {
private static MyObject myObject = new MyObject();
} private MyObject() {
} public static MyObject getInstance() {
return MyObjectHandler.myObject;
} }
180612070
180612070
180612070

  四、序列化与反序列化的单例模式实现

  静态内置类可以达到线程安全问题,但如果需要序列化对象时,使用默认的方式运行得到的结果还是多例的。

  示例1:不使用readResolve()方法时,得到的不是同一个对象。

package test;

import java.io.ObjectStreamException;
import java.io.Serializable; public class MyObject implements Serializable { private static final long serialVersionUID = 888L; //静态内置类方式
private static class MyObjectHandler {
private static final MyObject myObject = new MyObject();
} private MyObject() {
} public static MyObject getInstance() {
return MyObjectHandler.myObject;
} // protected Object readResolve() throws ObjectStreamException {
// System.out.println("调用了readResolve方法");
// return MyObjectHandler.myObject;
// } }
package test.run;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import test.MyObject; public class SaveAndRead { public static void main(String[] args) {
try {
MyObject myObject = MyObject.getInstance();
FileOutputStream fosRef = new FileOutputStream(new File("myObjectFile.txt"));
ObjectOutputStream oosRef = new ObjectOutputStream(fosRef);
oosRef.writeObject(myObject);
oosRef.close();
fosRef.close();
System.out.println(myObject.hashCode());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} try {
FileInputStream fisRef = new FileInputStream(new File("myObjectFile.txt"));
ObjectInputStream iosRef = new ObjectInputStream(fisRef);
MyObject myObject = (MyObject) iosRef.readObject();
iosRef.close();
fisRef.close();
System.out.println(myObject.hashCode());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} } }
1975012498
990368553

  示例2:在反序列化中使用readResolve()方法时,就会得到同一个对象。

package test;

import java.io.ObjectStreamException;
import java.io.Serializable; public class MyObject implements Serializable { private static final long serialVersionUID = 888L; //静态内置类方式
private static class MyObjectHandler {
private static final MyObject myObject = new MyObject();
} private MyObject() {
} public static MyObject getInstance() {
return MyObjectHandler.myObject;
} protected Object readResolve() throws ObjectStreamException {
System.out.println("调用了readResolve方法");
return MyObjectHandler.myObject;
} }
1975012498
调用了readResolve方法
1975012498

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

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

  示例:从输出结果也可以看出,使用静态代码块也可以解决问题。

package test;

public class MyObject {

    private static MyObject instance = null;

    private MyObject() {
} static {
instance = new MyObject();
} public static MyObject getInstance() {
return instance;
} }
686587801
686587801
686587801
686587801
686587801
686587801
686587801
686587801
686587801
686587801
686587801
686587801
686587801
686587801
686587801

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

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

  示例:利用public enum MyObject{}枚举类,可以实现单例设计模式。

package test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException; public enum MyObject {
connectionFactory; private Connection connection; private MyObject() {
try {
System.out.println("调用了MyObject的构造器");
String url = "jdbc:sqlserver://localhost:1079;databaseName=ghydb";
String username = "sa";
String password = "";
String driverName = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
Class.forName(driverName);
connection = DriverManager.getConnection(url, username, password);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
} public Connection getConnection() {
return connection;
}
}
package extthread;

import test.MyObject;

public class MyThread extends Thread {

    @Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(MyObject.connectionFactory.getConnection()
.hashCode());
}
}
}
调用了MyObject的构造器
686587801
686587801
686587801
686587801
686587801
686587801
686587801
686587801
686587801
686587801
686587801
686587801
686587801
686587801
686587801

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

  示例:六中的示例将枚举类进行曝露,违反了“职责单一原则”,修改MyObject类进行完善。

package test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException; public class MyObject { public enum MyEnumSingleton {
connectionFactory; private Connection connection; private MyEnumSingleton() {
try {
System.out.println("创建MyObject对象");
String url = "jdbc:sqlserver://localhost:1079;databaseName=y2";
String username = "sa";
String password = "";
String driverName = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
Class.forName(driverName);
connection = DriverManager.getConnection(url, username,
password);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
} public Connection getConnection() {
return connection;
}
} public static Connection getConnection() {
return MyEnumSingleton.connectionFactory.getConnection();
} }

  

Java多线程编程(六)单例模式与多线程的更多相关文章

  1. Java多线程编程核心技术---单例模式与多线程

    立即加载/饿汉模式 立即加载就是使用类的时候已经将对象创建完毕. public class MyObject { //立即加载方式==饿汉模式 private static MyObject myOb ...

  2. Java多线程编程总结一:多线程基本概念

    Java多线程编程总结一 – 初识多线程 进程.多进程.线程.多线程的概念 进程(process):CPU的执行路径.通俗的说就是系统中正在运行的程序.比如我们打开了浏览器.QQ等等,这些程序一旦被打 ...

  3. 5天玩转C#并行和多线程编程 —— 第五天 多线程编程大总结

    5天玩转C#并行和多线程编程系列文章目录 5天玩转C#并行和多线程编程 —— 第一天 认识Parallel 5天玩转C#并行和多线程编程 —— 第二天 并行集合和PLinq 5天玩转C#并行和多线程编 ...

  4. Java多线程核心技术(五)单例模式与多线程

    本文只需要考虑一件事:如何使单例模式遇到多线程是安全的.正确的 1.立即加载 / "饿汉模式" 什么是立即加载?立即加载就是使用类的时候已经将对象创建完毕,常见的实现办法就是直接 ...

  5. Java多线程编程实战02:多线程编程模型

    多线程编程模型 线程安全名词 串行.并发和并行 串行:一个人,将任务一个一个完成 并发:一个人,有策略地同时做多件事情 并行:多个人,每人做一个事情 竞态 名词 竞态:计算结果的正确性与时间有关的现象 ...

  6. C# 多线程编程第一步——理解多线程

    一.进程.线程及多线程的概念 什么是多线程呢?不理解. 那什么是线程呢?说到线程就不得不说说进程.我在网上搜索也搜索了一些资料,大部分所说的进程其实是很抽象的东西.通俗的来讲,进程就是一个应用程序开始 ...

  7. 【Java并发编程六】线程池

    一.概述 在执行并发任务时,我们可以把任务传递给一个线程池,来替代为每个并发执行的任务都启动一个新的线程,只要池里有空闲的线程,任务就会分配一个线程执行.在线程池的内部,任务被插入一个阻塞队列(Blo ...

  8. Java多线程编程之单例模式

    延迟加载:“懒汉模式” 延迟加载是指在调用getInstance()方法时创建实例.常见的方法是在getInstance()方法中实例化new.实现代码如下: 但是因为getInstance()中有多 ...

  9. Java并发编程(一)-- 多线程的基本概念

    多线程发展进程 在过去单CPU时代,单任务在一个时间点只能执行单一程序:发展到多任务阶段,计算机能在同一时间点并行执行多任务或多进程--虽然并不是真正意义上的“同一时间点”,而是多个任务或进程共享一个 ...

  10. java并发编程(八)多线程环境下安全使用集合

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17200509     在集合API中,最初设计的Vector和Hashtable是多线程安 ...

随机推荐

  1. Flask基础(04)-->相关配置参数

    # 导入Flask from flask import Flask from flask import config # 创建Flask的应用程序 app = Flask(__name__) #=== ...

  2. 实现一个3D图片轮播插件 —— 更新版

    前言:     前段时间写下了之前那篇 3D图片轮播效果,后来发现了 Pedro Botelho 写的jquery.gallery.js ,于是重新修改了自己的这个图片轮播,使之可以成为一个插件来使用 ...

  3. Thinkphp5.0第五篇

    原样输出 使用literal标签防止模板标签被解析 例如 {literal} {$name}<br/> {/literal} 模板单行注释 {//注释内容} 多行注释 {/*注释内容*/} ...

  4. [LeetCode] 1108. Defanging an IP Address

    Description Given a valid (IPv4) IP address, return a defanged version of that IP address. A defange ...

  5. JVM(三)初始化

    字节码指令和符号引用.直接引用 1.主动引用和被动引用 主动引用:虚拟机规定只有满足四个情况的的情况下,才会进行主动引用.    被动引用:除过四种情况的引用是被动引用.     只有主动引用才会初始 ...

  6. Django默认权限机制介绍及实践

    演示Django版本为当前最新版本v2.2 当Django配置文件中的INSTALL_APPS包含了django.contrib.auth时,就默认启用了一个简单的权限系统,提供了为用户或组分配权限的 ...

  7. 刷新:重新发现.NET与未来

    微软在比尔·盖茨手中创立并崛起, 成为PC互联网时代的霸主,很多70,80后都有MVP Ediwang的体验: 当时的微软对我来说就是神的存在.因为我认识电脑到使用电脑的一切几乎都离不开这家伟大的公司 ...

  8. php企业微信获取员工userid以及打卡信息

    企业微信可以通过部门列表获取部门下的员工信息,从而获取到员工的userid //首先获取需要的access_token $access_token = json_decode($this->ge ...

  9. 设置VMWare CentOS7虚拟机上网(配置静态地址)

    针对CentOS安装后设置虚拟机上网,参考网上相关资料和实际操作经验总结如下.本人亲测有效,进入主题. 设置虚拟机上网步骤 1.虚拟机设置->网络适配器 2.编辑->虚拟机网络编辑器-&g ...

  10. kali系统

    打开终端分别输入下面两条命令: update-alternatives --install /usr/bin/python python /usr/bin/python2 100 update-alt ...