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多线程(三)如何 ...
随机推荐
- zxing开源库的基本使用
如果你的项目中有模块跟二维码相关的话,那你一定听过或者用过大名鼎鼎的zxing开源库. 什么是zxing? ZXing是一个开源的,用Java实现的多种格式的1D/2D条码图像处理库,它包含了联系到其 ...
- ngx-moment汉化
1.导入汉化文件 import '../../../node_modules/moment/locale/zh-cn.js' 2.使用汉化 <span>{{item.time|amLoca ...
- socket网络编程之不间断通信
socket是python提供的一种网络通信方式. socket是应用层与TCP/IP协议通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议 ...
- 【Git 学习三】深入理解git reset 命令
重置命令(git reset)是Git 最常用的命令之一,也是最危险最容易误用的命令.来看看git reset命令用法. --------------------------------------- ...
- 基数排序python实现
基数排序python实现 基数排序 基数排序(英语:Radix sort)是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较.由于整数也可以表达字符串(比如名字或 ...
- 如何修改discuz论坛的图像地址
今天帮别人修改discuz论坛,遇到一个问题,就是图像显示不出来,按F12键后,发现是自己的图像路径设置有问题,于是就要去修改这个设置路径了.有两种方法: 一,直接修改配置文件,打开config/co ...
- Docker 从入门到实践(一)Docker 简介
读前须知:本教程大部分都是[Docker 从入门到实践 ]一书的知识,有兴趣可以直接观看书籍.同时,借鉴书籍的知识,如有侵权,请告知我,我会删除处理.谢谢. 一.什么是 Docker? Docker ...
- elixir mix 简介
概述 mix 是 elixir 工程的构建工具,利用 mix,可以快速方便的创建 elixir 工程,写单元测试,管理 elixir 包的依赖管理等等. 我觉得刚开始学习 elixir 的时候,先简单 ...
- 汲取营养的blog专栏
网路上博客专栏是学习提升.思考深化的好途径,目前发现的博客价值高的平台: (1)EETOP www.eetop.cn (2)CSND www.csdn.net (3)cnblog www.cnblog ...
- win32gui.Findwindow(parm1,parm2)查找窗口的句柄方法
介绍之前先让大家了解一下win32gui的模块用法 和 获取窗口类名工具 使用Python时,有时也会要操作到系统窗口的一些东西,下面就介绍win32gui.Findwindow(param1,par ...