Java多线程核心技术(五)单例模式与多线程
本文只需要考虑一件事:如何使单例模式遇到多线程是安全的、正确的
1.立即加载 / "饿汉模式"
什么是立即加载?立即加载就是使用类的时候已经将对象创建完毕,常见的实现办法就是直接 new 实例化。
public class MyObject {
private static MyObject myObject = new MyObject();
public MyObject(){
}
public static MyObject getInstance(){
return myObject;
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
}
}
打印结果:
985396398
985396398
985396398
控制台打印的 hashCode 是同一个值,说明对象是同一个,也就实现了立即加载型单例设计模式。
此版本的缺点是不能有其他其他实例变量,因为getInstance()方法没有同步,所以有可能出现非线程安全问题。
2.延迟加载 / "懒汉模式"
什么是延迟加载?延迟加载就是在调用 get() 方法时实例才被创建,常见的实现方法就是在 get() 方法中进行 new() 实例化。
测试代码:
public class MyObject {
private static MyObject myObject;
public MyObject() {
}
public static MyObject getInstance() {
try {
if (myObject == null) {
//模拟对象在创建之前做的一些准备工作
Thread.sleep(3000);
myObject = new MyObject();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
}
}
打印结果:
985396398
610025186
21895028
从运行结果来看,创建了三个对象,并不是真正的单例模式。原因显而易见,3个线程同时进入了if (myObject == null) 判断语句中,最后各自都创建了对象。
3.延迟加载解决方案
3.1 声明synchronized关键字
既然多个线程可以同时进入getInstance() 方法,那么只需要对其进行同步synchronized处理即可。
public class MyObject {
private static MyObject myObject;
public MyObject() {
}
synchronized public static MyObject getInstance() {
try {
if (myObject == null) {
//模拟对象在创建之前做的一些准备工作
Thread.sleep(3000);
myObject = new MyObject();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
}
}
打印结果:
961745937
961745937
961745937
虽然运行结果表明,成功实现了单例,但这种给整个方法上锁的解决方法效率太低。
3.2 尝试同步 synchronized 代码块
同步方法是对方法的整体加锁,这对运行效率来讲很不利的。改成同步代码块后:
public class MyObject {
private static MyObject myObject;
public MyObject() {
}
public static MyObject getInstance() {
try {
synchronized (MyObject.class) {
if (myObject == null) {
//模拟对象在创建之前做的一些准备工作
Thread.sleep(3000);
myObject = new MyObject();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
}
}
打印结果:
355159803
355159803
355159803
运行结果虽然表明是正确的,但同步synchronized语句块依旧把整个 getInstance()方法代码包括在内,和synchronize 同步方法效率是一样低下。
3.3 针对某些重要的代码进行单独同步
所以,我们可以针对某些重要的代码进行单独的同步,而其他的代码则不需要同步。这样在运行时,效率完全可以得到大幅度提升。
public class MyObject {
private static MyObject myObject;
public MyObject() {
}
public static MyObject getInstance() {
try {
if (myObject == null) {
//模拟对象在创建之前做的一些准备工作
Thread.sleep(3000);
synchronized (MyObject.class) {
myObject = new MyObject();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
}
}
运行结果:
985396398
21895028
610025186
此方法只对实例化对象的关键代码进行同步,从语句的结构上来说,运行的效率的确得到的提升。但是在多线程的情况下依旧无法解决得到一个单例对象的结果。
3.4 使用DCL双检查锁机制
在最后的步骤中,使用DCL双检查锁机制来实现多线程环境中的延迟加载单例设计模式。
public class MyObject {
private volatile static MyObject myObject;
public MyObject() {
}
public static MyObject getInstance() {
try {
if (myObject == null) {
//模拟对象在创建之前做的一些准备工作
Thread.sleep(3000);
synchronized (MyObject.class) {
if (myObject == null) {
myObject = new MyObject();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
}
}
运行结果:
860826410
860826410
860826410
使用DCL双重检查锁功能,成功地解决了“懒汉模式”遇到多线程的问题。DCL也是大多数多线程结合单例模式使用的解决方案。
4.使用静态内置类实现单例模式
DCL可以解决多线程单例模式的非线程安全问题。当然,还有许多其它的方法也能达到同样的效果。
public class MyObject {
public static class MyObjectHandle{
private static MyObject myObject = new MyObject();
public static MyObject getInstance() {
return myObject;
}
}
public static MyObject getInstance(){
return MyObjectHandle.getInstance();
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
}
}
打印结果:
1035057739
1035057739
1035057739
静态内置类可以达到线程安全问题,但如果遇到序列化对象时,使用默认的方式运行得到的结果还是多例的。
解决方法就是在反序列化中使用readResolve()方法:
public class MyObject implements Serializable {
//静态内部类
public static class MyObjectHandle{
private static final MyObject myObject = new MyObject();
}
public static MyObject getInstance(){
return MyObjectHandle.myObject;
}
protected Object readResolve(){
System.out.println("调用了readResolve方法");
return MyObjectHandle.myObject;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
MyObject myObject = MyObject.getInstance();
FileOutputStream outputStream = new FileOutputStream(new File("myObject.txt"));
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(myObject);
objectOutputStream.close();
System.out.println(myObject.hashCode());
FileInputStream inputStream = new FileInputStream(new File("myObject.txt"));
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
MyObject object = (MyObject) objectInputStream.readObject();
objectInputStream.close();
System.out.println(object.hashCode());
}
}
运行结果:
621009875
调用了readResolve方法
621009875
5.使用static代码块实现单例模式
静态代码块中的代码在使用类的时候就已经执行了,所以可以应用静态代码块的这个特点来实现单例设计模式。
public class MyObject {
private static MyObject myObject = null;
static {
myObject = new MyObject();
}
public static MyObject getInstance(){
return myObject;
}
public static void main(String[] args){
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
}
}
运行结果:
355159803
355159803
355159803
6.使用enum枚举数据类型实现单例模式
枚举enum 和静态代码块的特性相似,在使用枚举类时,构造方法会被自动调用,也可以应用其这个特性实现单例设计模式。
public enum Singleton {
INSTANCE;
private MyObject myObject = null;
Singleton() {
myObject = new MyObject();
}
public MyObject getInstance(){
return myObject;
}
public static void main(String[] args){
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Singleton.INSTANCE.getInstance().hashCode());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Singleton.INSTANCE.getInstance().hashCode());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Singleton.INSTANCE.getInstance().hashCode());
}
}).start();
}
}
运行结果:
1516133987
1516133987
1516133987
这样实现的一个弊端就是违反了“职责单一原则”,完善后的代码如下:
public class MyObject {
public enum Singleton {
INSTANCE;
private MyObject myObject = null;
Singleton() {
myObject = new MyObject();
}
public MyObject getInstance() {
return myObject;
}
}
public static MyObject getInstance(){
return Singleton.INSTANCE.getInstance();
}
public static void main(String[] args){
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}).start();
}
}
运行结果:
610025186
610025186
610025186
7.文末总结
本文使用若干案例来阐述单例模式与多线程结合遇到的情况与解决方案。
参看
《Java多线程编程核心技术》高洪岩著
扩展
Java多线程核心技术(五)单例模式与多线程的更多相关文章
- JAVA学习第二十六课(多线程(五))- 多线程间的通信问题
一.线程间的通信 实例代码: 需求是:输入一个姓名和性别后,就输出一个姓名和性别 class Resource { String name; String sex ; } class Input im ...
- Java第二十五天,多线程之等待唤醒机制
当线程被创建并且被启动之后,它既不是一启动就进入了执行状态,也不是一直处于执行状态,而是具有以下多种状态: 这六种状态之间的转换关系如下: 1.等待唤醒机制 注意: (1)两个线程之间必须用同步代码块 ...
- Java多线程核心技术(六)线程组与线程异常
本文应注重掌握如下知识点: 线程组的使用 如何切换线程状态 SimpleDataFormat 类与多线程的解决办法 如何处理线程的异常 1.线程的状态 线程对象在不同运行时期有不同的状态,状态信息就处 ...
- Java多线程核心技术(四)Lock的使用
本文主要介绍使用Java5中Lock对象也能实现同步的效果,而且在使用上更加方便. 本文着重掌握如下2个知识点: ReentrantLock 类的使用. ReentrantReadWriteLock ...
- Java多线程编程核心技术-第6章-单例模式与多线程-读书笔记
第 6 章 单例模式与多线程 本章主要内容 如何使单例模式遇到多线程是安全的.正确的. 6.1 立即加载 / “饿汉模式” 什么是立即加载?立即加载就是使用类的时候已经将对象创建完毕,常见的实现办法就 ...
- 从ConcurrentHashMap的演进看Java多线程核心技术 Java进阶(六)
本文分析了HashMap的实现原理,以及resize可能引起死循环和Fast-fail等线程不安全行为.同时结合源码从数据结构,寻址方式,同步方式,计算size等角度分析了JDK 1.7和JDK 1. ...
- Java多线程编程(六)单例模式与多线程
在使用多线程技术的单例模式时会出现一些意想不到的情况,这样的代码如果在生产环境中出现异常,有可能造成灾难性的后果. 一.立即加载/“饿汉模式” 立即加载就是使用类的时候已经将对象创建完毕,常见的实现方 ...
- java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析
java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java ...
- Java多线程(五)线程的生命周期
点我跳过黑哥的卑鄙广告行为,进入正文. Java多线程系列更新中~ 正式篇: Java多线程(一) 什么是线程 Java多线程(二)关于多线程的CPU密集型和IO密集型这件事 Java多线程(三)如何 ...
随机推荐
- C++基础概述
阅读Android源码需要对C++基础语法有一定的认识,借此对C++做一个简单的语法认知. 1.数据类型 类型 关键字 布尔型 bool 字符型 char 整型 int 浮点型 float 双浮点型 ...
- Android 与Java 进程退出 killProcess与System.exit
android所有activity都在主进程中,在清单文件Androidmanifest.xml中可以设置启动不同进程,Service需要指定运行在单独进程?主进程中的主线程?还是主进程中的其他线程? ...
- (后端)mybatis中使用Java8的日期LocalDate、LocalDateTime
原文地址:https://blog.csdn.net/weixin_38553453/article/details/75050632 MyBatis的型处理器是属性“createdtime参数映射为 ...
- Maven和Solr简单总结
一.1.Maven介绍 Maven是一个项目管理工具,Maven通过POM项目对象模型,对象项目进行管理,通过一个配置文件(xml文件)进行项目的管理.对象项目的声明周期中每个阶段进行管理(清理,编译 ...
- 如何获取Azure AD tenant的tenant Id?
一般情况下,Azure AD用户知道自己tenant域名,因为域名是账户的后缀,例如:contoso.onMicrosoft.com.如果你还不了解什么是Azure AD tenant,可 ...
- django数据查询之F查询和Q查询
仅仅靠单一的关键字参数查询已经很难满足查询要求.此时Django为我们提供了F和Q查询: # F 使用查询条件的值,专门取对象中某列值的操作 # from django.db.models impor ...
- c/c++ 智能指针 shared_ptr 使用
智能指针 shared_ptr 使用 上一篇智能指针是啥玩意,介绍了什么是智能指针. 这一篇简单说说如何使用智能指针. 一,智能指针分3类:今天只唠唠shared_ptr shared_ptr uni ...
- 用好lua+unity,让性能飞起来——lua与c#交互篇
前言 在看了uwa之前发布的<Unity项目常见Lua解决方案性能比较>,决定动手写一篇关于lua+unity方案的性能优化文. 整合lua是目前最强大的unity热更新方案,毕竟这是唯一 ...
- Apache中httpd.conf文件的详解
PHP中,Apache的配置至关重要,特别是httpd.conf这个文件,它是Apache中的核心文件.好了,废话不说,今天将这个文件中的一些内容讲解一番. ServerRoot "d:/w ...
- [福大软工] Z班 团队Beta阶段成绩汇总
Beta敏捷冲刺得分 队伍名 1 2 3 4 5 总分 Dipper 10 10 10 10 10 50 SWSD 9 9 9 9 7 43 五成胜算 10 10 10 10 10 50 人月神教 0 ...