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

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

  立即加载就是使用类的时候已经将对象创建完毕,常见的实现方法就是直接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. 品Spring:详细解说bean后处理器

    一个小小的里程碑 首先感谢能看到本文的朋友,感谢你的一路陪伴. 如果每篇都认真看的话,会发现本系列以bean定义作为切入点,先是详细解说了什么是bean定义,接着又强调了bean定义为什么如此重要. ...

  2. Spring boot - 梳理 - 根本上说,Spring Boot项目只不过是一个普通的Spring项目,只是使用了Spring Boot的起步依赖和自动配置

    根本上说,Spring Boot项目只不过是一个普通的Spring项目,只是使用了Spring Boot的起步依赖和自动配置

  3. linux 防火墙相关命令

    1.系统命令systemctl start firewalld #启动 systemctl status firewalld #查看运行状态 systemctl stop firewalld #关闭 ...

  4. java多线程技术核心

    1.进程的三大特征: 独立性:拥有自己的独立的地址空间,一个进程不可以直接去访问其他进程的地址空间. 动态性:是一个系统中活动的指令的集合. 并发性:单个进程可以在多个处理器上并发进行,互不影响. 2 ...

  5. setfacl、getfacl

    当用户访问一个文件时,权限匹配的顺序为owner--->group--->other.当设置访问控制列表后,owner--->facl_user--->group---> ...

  6. 同时支持EF+Dapper的混合仓储,助你快速搭建数据访问层

    背景 17年开始,公司开始向DotNet Core转型,面对ORM工具的选型,当时围绕Dapper和EF发生了激烈的讨论.项目团队更加关注快速交付,他们主张使用EF这种能快速开发的ORM工具:而在线业 ...

  7. Docker详解(四) — Dockerfile剖析

    目录 1.Dockfile简介 2. Dockerfile构建过程解析 3. Dockerfile体系结构 4. 案例 4.1 自定义mycentos 4.2 CMD/ENTRYPOINT 镜像案例 ...

  8. Elasticsearch全文检索学习

    ElasticSearch官方网址:https://www.elastic.co ElasticSearch官方网址(中文):https://www.elastic.co/cn/ Elasticsea ...

  9. WebGL简易教程(十):光照

    目录 1. 概述 2. 原理 2.1. 光源类型 2.2. 反射类型 2.2.1. 环境反射(enviroment/ambient reflection) 2.2.2. 漫反射(diffuse ref ...

  10. Hbase入门(五)——客户端(Java,Shell,Thrift,Rest,MR,WebUI)

    Hbase的客户端有原生java客户端,Hbase Shell,Thrift,Rest,Mapreduce,WebUI等等. 下面是这几种客户端的常见用法. 一.原生Java客户端 原生java客户端 ...