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

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

  立即加载就是使用类的时候已经将对象创建完毕,常见的实现方法就是直接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. [phyton]文件的简单读写练习

    f.open() 用于打开一个文件. f=open("record.txt","w",encoding="utf-8")#打开文件,设置文件 ...

  2. Android Studio [WebView]

    WebViewActivity.java package com.xdw.a122; import android.graphics.Bitmap; import android.support.v7 ...

  3. 机器学习之SVM调参实例

    一.任务 这次我们将了解在机器学习中支持向量机的使用方法以及一些参数的调整.支持向量机的基本原理就是将低维不可分问题转换为高维可分问题,在前面的博客具体介绍过了,这里就不再介绍了. 首先导入相关标准库 ...

  4. HTML5-常用正则表达式

    有关H5正则表达式的一些常用式子,希望热爱编程的同学们多多指教,还有也希望可以关注收藏本站哦!❤^_^❤ 一.校验数字的表达式 1. 数字:^[0-9]*$ 2. n位的数字:^\d{n}$ 3. 至 ...

  5. Hbase入门(三)——数据模型

    Hbase最核心但也是最难理解的就是数据模型,由于与传统的关系型数据库不同,虽然Hbase也有表(Table),也有行(Row)和列(Column),但是与关系型数据库不同的是Hbase有一个列族(C ...

  6. Eureka参数配置->Client端参数

     1.基本参数配置列表: 参数 默认值 说明 eureka.client.availability-zones    告知client有哪些region及availability-zones,支持配置 ...

  7. 蓝松短视频SDK支持AE模板, 可做类似微商视频, 小柿饼的效果等

    AE模板: 是指设计师用Adobe After Effect做好各种视频动画,比如炫酷视频,文艺/搞笑的场景,相册效果等,根据我们的指导文件导出.蓝松SDK会解析导出的文件,自动还原成AE设计时的动画 ...

  8. Git基础概念与Flow流程介绍

    目录 Git相关 基本概念 常见客户端 TortoiseGit Sourcetree Intellij Idea 命令行 常用命令 存储区域 命令之 add & commit &pus ...

  9. springcloud -- sleuth+zipkin整合rabbitMQ详解

    为什么使用RabbitMQ? 我们已经知道,zipkin的原理是服务之间的调用关系会通过HTTP方式上报到zipkin-server端,然后我们再通过zipkin-ui去调用查看追踪服务之间的调用链路 ...

  10. Openshift创建Router和Registry

    Openshift创建Router和Registry: [root@DockerServer openshift]# oadm policy add-scc-to-user privileged sy ...