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多线程(三)如何 ...
随机推荐
- MS SQL作业Schedule的限制注意事项
最近遇到了一个关于MS SQL作业Schedule下有限制的特殊案例,有一个作业,用户要求执行的时间为:9:30,14:30,16:30, 19:00,于是我设置了两个Schedule,其中一个每 ...
- selenium.common.exceptions.WebDriverException: Message: 'chromedriver' executable needs to be in PATH.
解决办法: 把chromedriver exe文件放到python scripts目录下
- java.lang.IllegalStateException: Cannot forward after response has been committed的一个情况解决方法
java.lang.IllegalStateException: Cannot forward after response has been committed xxx.xxx.doPost(upd ...
- Linux用户抢占和内核抢占详解(概念, 实现和触发时机)--Linux进程的管理与调度(二十)
1 非抢占式和可抢占式内核 为了简化问题,我使用嵌入式实时系统uC/OS作为例子 首先要指出的是,uC/OS只有内核态,没有用户态,这和Linux不一样 多任务系统中, 内核负责管理各个任务, 或者说 ...
- c/c++ 智能指针 unique_ptr 使用
智能指针 unique_ptr 使用 和shared_ptr不同,可以有多个shared_ptr指向同一个内存,只能有1个unique_ptr指向某个内存.因此unique_ptr不支持普通的拷贝和赋 ...
- JavaScript -- 时光流逝(九):Window 对象、Navigator 对象
JavaScript -- 知识点回顾篇(九):Window 对象.Navigator 对象 1. Window 对象 1.1 Window 对象的属性 (1) closed: 返回窗口是否已被关闭. ...
- 【2018.05.09 Python学习及实践】个人项目中使用的Python库备忘-持续更新
科研中无论是使用C/C++.Python.Matlab,如果能找到合适的库可谓是事半功倍: 有时候忙活半天才发现本身就有成熟的库可用,自己实现的在功能.性能.安全性上都远远不及,虽然锻炼了能力,但存在 ...
- C语言 文件的读写操作
//凯鲁嘎吉 - 博客园 http://www.cnblogs.com/kailugaji/ #include<stdio.h> #include<stdlib.h> void ...
- JDK动态代理和cglib代理详解
JDK动态代理 先做一下简单的描述,通过代理之后返回的对象已并非原类所new出来的对象,而是代理对象.JDK的动态代理是基于接口的,也就是说,被代理类必须实现一个或多个接口.主要原因是JDK的代理原理 ...
- HIVE配置错误信息
原因:版本问题 解决方法:cp /root/hive/lib/当前的jlinexx.jar /root/hadoop/share/hadoop/yarn/lib