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

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

  立即加载就是使用类的时候已经将对象创建完毕,常见的实现方法就是直接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. java ThreadLocal使用

    1.源码分析 此处以JDK1.8版本分析 1.1 set方法 /** * Sets the current thread's copy of this thread-local variable * ...

  2. Linux 删除命令rm

    Linux rm命令用于删除一个文件或者目录. 语法:rm [options] name... 参数: 1.-i 删除前逐一询问确认. 2.-f 即使原档案属性设为唯读,亦直接删除,无需逐一确认. 3 ...

  3. springboot值mybatis 别名等配置

    在application配置文件中添加如下: mybatis: #该配置替换在sql-config-map中的typeAliases配置 type-aliases-package: com.ww.ww ...

  4. MongoDB的复制源oplog

    ​ 之前有说过MongoDB的复制是异步复制的,其实也就是通过oplog来实现的,他存放在local数据库中,我们来查询一下主节点的日志大小. ​ 除了主节点有oplog之外,其他节点也就有oplog ...

  5. 应用角度看kafka的术语和功能

    kafka的术语(Terminology) Topic 和Consumer Group Topic 每条发布到 Kafka 集群的消息都有一个类别,这个类别被称为 Topic.(物理上不同 Topic ...

  6. 如何使用CSS实现居中

    前言: 这一篇主要是翻译 <how-to-center-anything-with-css>这一篇文章的主要内容,再加上自己的一些概括理解:主要问题是解决垂直居中的问题.我们知道实现水平居 ...

  7. python串口助手

    最近项目中要使用模拟数据源通过向外发送数据,以前都是用C#编写,最近在研究python,所以就用python写了一个串口助手,方便以后的测试. 在电脑上通过虚拟串口助手产生两个虚拟串口,运行编写的串口 ...

  8. 手把手教你如何在window下将jenkins+allure集成生成的测试报告通过jenkins配置邮箱自动发送-04(非常详细,非常实用)

    简介 上一篇生成测试报告,小伙伴们和童鞋们就又问道,测试报告已经生成了,怎么发送给相关的负责人了?小伙伴们和童鞋们不要着急,听宏哥慢慢给你道来,心急吃不了热豆腐哈.今天这篇文章宏哥就给小伙伴和童鞋们来 ...

  9. PHP生成唯一ID的方法

    PHP自带生成唯一id的函数:uniqid() 它是基于当前时间微秒数的 用法如下: echo uniqid(); //13位的字符串 echo uniqid("php_"); / ...

  10. 洛谷 P2145 [JSOI2007]祖码

    题目描述 这是一个流行在Jsoi的游戏,名称为祖玛. 精致细腻的背景,外加神秘的印加音乐衬托,彷佛置身在古老的国度里面,进行一个神秘的游戏——这就是著名的祖玛游戏.祖玛游戏的主角是一只石青蛙,石青蛙会 ...