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多线程(三)如何 ...
随机推荐
- Android JNI中C调用Java方法
背景需求 我们需要在JNI的C代码调用Java代码.实现原理:使用JNI提供的反射借口来反射得到Java方法,进行调用. JNI关键方法讲解. 1. 在同一个类中,调用其他方法 JNIEXPORT v ...
- OkHttp3源码详解(六) Okhttp任务队列工作原理
1 概述 1.1 引言 android完成非阻塞式的异步请求的时候都是通过启动子线程的方式来解决,子线程执行完任务的之后通过handler的方式来和主线程来完成通信.无限制的创建线程,会给系统带来大量 ...
- View体系之属性动画
(内容省略了valueAnimator和PropertyValueHolder使用) 属性动画的使用的主要方式是AnimatorSet和ObjectAnimator配合使用.ObjectAnimato ...
- Android自定义多宫格解锁控件
在此之前,一直在想九宫格的实现方法,经过一个上午的初步研究终于完成了一个简单的N*N的宫格解锁组件,代码略显粗糙,仅仅做到简单的实现,界面等后期在做优化,纯粹是学习的目的,在算法上有点缺陷,如果有错误 ...
- c# 建立到数据源的连接 以及获取项目配置文件的属性
两种连接数据库的写法: <connectionStrings> <add name="HRModelsContainer" connectionString=&q ...
- java 一个实例
this 代替
- 【2018.04.19 ROS机器人操作系统】机器人控制:运动规划、路径规划及轨迹规划简介之一
参考资料及致谢 本文的绝大部分内容转载自以下几篇文章,首先向原作者致谢,希望自己能在这些前辈们的基础上能有所总结提升. 1. 运动规划/路径规划/轨迹规划的联系与区别 https://blog.csd ...
- css点滴2—六种方式实现元素水平居中
本文参考文章<六种方式实现元素水平居中> 元素水平居中的方法,最常见的莫过于给元素一个显式的宽度,然后加上margin的左右值为auto.这种方式给固定宽度的元素设置居中是最方便不过的.但 ...
- 《软工实践》第零次作业 - 一些QA
<软工实践>第零次作业 - 一些QA Q&A (1)回想一下你初入大学时对计算机专业的畅想 当初你是如何做出选择计算机专业的决定的? 你认为过去两年中接触到的课程是否符合你对计算机 ...
- 【Teradata UDF】MD5加密
使用参考:Teradata自定义函数UDF(chs_instr) 源码下载:百度网盘链接