JAVA_多线程_单例模式
这篇是入职之后的第二篇了,上一篇我简单介绍了一下LOCK里面的类的方法,感兴趣的话可以去了解一下,以后坚持每周至少会更新一篇关于多线程方面的文章,希望博友们可以一起加油成长。
这篇主要的内容是单例模式在多线程环境下的设计,这篇算是比较重要的内容,我会进行文字和代码的共同说明来讲解记录
1、立即加载(饿汉模式)
说到标题,有人会说什么是立即加载呢?立即加载就是使用类的时候已经将对象创建完毕了,比如说直接new实例化对象。也就是在调用方法之前,实例已经被创建了
public class MyObject {
private static MyObject myObject = new MyObject();
private MyObject(){
}
public static MyObject getInstance(){
return myObject;
}
}
看这段代码,他的缺点是不能有其他实例变量。外部的使用者需要使用MyObject实例的时候,只能通过getInstance方法,另外假如没有用到这个实例的时候,他已经创建了出现,会有资源浪费的情况出现的。还有因为getInstance方法没有同步,所以有可能出现非线程安全的问题。
2、延迟加载(懒汉模式)
延迟加载就是在调用要使用的那个方法(假如MyObject方法)的时候实例才会被创建,实现方法就是在MyObject方法里面进行new实例化。
public class MyObject {
private static MyObject myObject;
private MyObject(){
}
public static MyObject getInstance(){
if(myObject==null){
myObject = new MyObject();
}
return myObject;
}
}
此代码虽然取得了一个对象,没毛病。单例!但是如果在多线程情况下,就会取出多个实例的情况,这个是与单例模式的初衷背道而驰的。
public class MyObject {
private static MyObject myObject;
private MyObject(){
}
public static MyObject getInstance(){
if(myObject==null){
try {
Thread.sleep(3000);
myObject = new MyObject();
}catch(Exception e){
e.printStackTrace();
}
}
return myObject;
}
}
可以试着自己多建立几个线程,运行一下这段代码,发现在多线程环境在建立出来了多个实例(可以打印对象的hashcode值进行比较)
那我们应该怎么去解决这个问题呢?
3、延迟加载(懒汉模式)——synchronized
public class MyObject {
private static MyObject myObject;
private MyObject(){
}
synchronized public static MyObject getInstance(){
if(myObject==null){
try {
Thread.sleep(3000);
myObject = new MyObject();
}catch(Exception e){
e.printStackTrace();
}
}
return myObject;
}
}
这样OK?看上去是的,是解决了得到了相同的实例,但是见到了synchronized这个东西,你不得犹豫一下么?加在了整个方法上啊,如果这个方法设计到比如说很多的过程或者运算,下一个线程想要取得对象,不是要等到程序员找到女朋友才行么。也就是要等到上一个线程释放锁之后,才可以继续进行。
有人说,我不加全部,我加部分不行么?
public static MyObject getInstance(){
synchronized (MyObject.class) {
if (myObject == null) {
try {
Thread.sleep(3000);
myObject = new MyObject();
} catch (Exception e) {
e.printStackTrace();
}
}
}
return myObject;
}
}
你仔细看,有啥大的变化么,并没有吧。因为效率还是一样的低低低低。每次我调用getIstance的时候是不是还要同步啊,所以太大变化啦
然后机智的我想出了这样的方法
public class MyObject {
private static MyObject myObject;
private MyObject(){
}
public static MyObject getInstance(){
if (myObject == null) {
try {
Thread.sleep(3000);
synchronized (MyObject.class) {
myObject = new MyObject();
}
} catch (Exception e) {
e.printStackTrace();
}
}
return myObject;
}
}
我只加载了需要创建对象的那个关键地方,看到了么,这样效率大大滴提升了。但是,重点来了,我靠,打印出来了两个不同对象,打印出来的对象hashcode值不一样了啊,不是一个对象了,因为两个线程都进入了if语句内,之后没有在进行判断,所以创建了两个对象。我单例什么呢?所以这个方法也pass
那我到底该怎么办呢?别着急,DCL双重检查所机制,不废话直接看代码
public class MyObject {
private static MyObject myObject;
private MyObject(){
}
public static MyObject getInstance(){
if (myObject == null) {
try {
Thread.sleep(3000);
synchronized (MyObject.class) {
if(myObject == null) {
myObject = new MyObject();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
return myObject;
}
}
哇,终于有方法可以实现单例模式在多线程环境下的正常工作了,哈哈哈哈哈,但是但是那你们就打错特错了。看如下分析
从JVM的角度来说,怎么创建一个对象呢?第一是申请一块内存,调用构造方法进行初始化操作,第二是分配一个指针指向这块内存。这两个操作谁在前面,谁在后面JVM并不会管它。那么就存在这么一种情况,JVM是先开辟出一块内存,然后把指针指向这块内存,最后调用构造方法进行初始化。
线程A开始创建MyObject的实例,此时线程B调用了getInstance()方法,首先判断MyObject是否为null。假设A已经把MyObject指向了那块内存,只是还没有调用构造方法,因此B检测到MyObject不为null,于是直接把MyObject返回了——问题出现了,尽管MyObject不为null,但它并没有构造完成结束。此时,如果B在A将MyObject构造完成之前就是用了这个实例,程序就会出现错误了!(其实在private static MyObject myObject; 改为 private volatile static MyObject myObject; 就不会发生这样的结果了。被volatile修饰的写变量不能和之前的读写代码调整,这里我们当做这个关键字不存在,以后会有专门的篇幅去详细讲解这个关键字的,这个关键字的坑有许多,我们慢慢踩)
那我们到底的咋整啊?
public class MyObject {
private static MyObject myObject;
private MyObject(){
}
public static MyObject getInstance(){
if (myObject == null) {
MyObject my;
synchronized (MyObject.class) {
my = myObject;
if (my == null) {
synchronized (MyObject.class) {
if (my == null) {
my = new MyObject();
}
}
myObject = my;
}
}
}
return myObject;
}
}
我们在第一个同步块里面创建一个临时变量,然后使用这个临时变量进行对象的创建,并且在最后把myObject指针临时变量的内存空间。写出这种代码基于以下思想,即synchronized会起到一个代码屏蔽的作用,同步块里面的代码和外部的代码没有联系。因此,在外部的同步块里面对临时变量my进行操作并不影响myObject,所以外部类在myObject=my;之前检测myObject的时候,结果myObject依然是null。
由于同步块的释放保证在此之前——也就是同步块里面——的操作必须完成,但是并不保证同步块之后的操作不能因编译器优化而调换到同步块结束之前进行。因此,编译器完全可以把myObject=my;这句移到内部同步块里面执行。又错了。
4、内部类实现方式
public class MyObject_inner {
private static class MyObjectHandler{
private static MyObject_inner myObject_inner = new MyObject_inner();
}
private MyObject_inner(){}
public static MyObject_inner getInstance(){
return MyObjectHandler.myObject_inner;
}
}
在这一版本的单例模式实现代码中,我们使用了Java的静态内部类。这一技术是被JVM明确说明了的,因此不存在任何二义性。在这段代码中,因为Myobject_inner没有static的属性,因此并不会被初始化。直到调用getInstance()的时候,会首先加载MyObjectHandler类,这个类有一个static的MyObject_inne实例,因此需要调用MyObject_inne的构造方法,然后getInstance()将把这个内部类的myobject_inner返回给使用者。由于这个myobject_inner是static的,因此并不会构造多次。
由于MyObjectHandler是私有静态内部类,所以不会被其他类知道,同样,static语义也要求不会有多个实例存在。并且,JSL规范定义,类的构造必须是原子性的,非并发的,因此不需要加同步块。同样,由于这个构造是并发的,所以getInstance()也并不需要加同步。
但是这种情况完全是对的么?假如遇到序列化的对象呢?会是什么样的结果?
5、序列化与反序列化的单例模式的实现
静态内部类可以达到线程安全的问题,但是如果遇到序列化对象的时候,使用默认的方式运行得到的结果还是多例的。
具体是为什么在序列化的时候不是单例的,本人我掌握的不太好,后续会把其中涉及到的知识补充之后,在完善此篇文章。
序列化会通过反射调用无参数的构造方法创建一个新的对象。解决的方式就是在implements Serializable这个方法里面加上这段代码
import java.io.Serializable;
public class MyObject_inner implements Serializable{
private static final long seriaVersionUID = 8899L;
private static class MyObjectHandler{
private static MyObject_inner myObject_inner = new MyObject_inner();
}
private MyObject_inner(){}
public static MyObject_inner getInstance(){
return MyObjectHandler.myObject_inner;
}
private Object readResolve(){
return MyObjectHandler.myObject_inner;
}
}
6、静态代码块实现单例模式
public class MyObject_inner {
private static MyObject_inner instance = null;
private MyObject_inner(){}
static {
instance = new MyObject_inner();
}
public static MyObject_inner getInstance(){
return instance;
}
}
7、枚举方法实现单例模式
public class MyObject_enum {
public enum EnumSingleton{
Instance;
private MyObject_enum instance;
EnumSingleton(){
instance = new MyObject_enum();
}
public MyObject_enum getInstance(){
return instance;
}
}
}
获取资源的方式很简单,只要 EnumSingleton.INSTANCE.getInstance() 即可获得所要实例。下面我们来看看单例是如何被保证的:
首先,在枚举中我们明确了构造方法限制为私有,在我们访问枚举实例时会执行构造方法,同时每个枚举实例都是static final类型的,也就表明只能被实例化一次。在调用构造方法时,我们的单例被实例化。也就是说,因为enum中的实例被保证只会被实例化一次,所以我们的INSTANCE也被保证实例化一次。枚举也提供了序列化机制。所以单元素的枚举类型已经成为了实现单例模式的最佳方法
这篇文章到此暂时先告一段落,里面有一点设计的序列化与反序列化实现单例模式我以后会在继续的更新补充进去的。 欢迎各位博友批评指正
JAVA_多线程_单例模式的更多相关文章
- java_设计模式_单例模式_Singleton Pattern(2016-08-04)
概念: 单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例. 适用场景: 在计算机系统中,线程池.缓存.日志对象.对话框.打印机.显卡的驱动程序对象常被设计成单例.这些应用都或多或 ...
- java多线程与单例模式(Singleton)不得不说的故事
转发自:http://blog.csdn.net/ligang7560/article/details/50890282 单例模式的多种实现方式 我们都知道单例模式有几种常用的写法: - 饿汉模式 - ...
- iOS开发多线程篇—单例模式(ARC)
iOS开发多线程篇—单例模式(ARC) 一.简单说明: 设计模式:多年软件开发,总结出来的一套经验.方法和工具 java中有23种设计模式,在ios中最常用的是单例模式和代理模式. 二.单例模式说明 ...
- Java多线程_生产者消费者模式2
在我的上一条博客中,已经介绍到了多线程的经典案列——生产者消费者模式,但是在上篇中用的是传统的麻烦的非阻塞队列实现的.在这篇博客中我将介绍另一种方式就是:用阻塞队列完成生产者消费者模式,可以使用多种阻 ...
- 多线程安全单例模式学习代码 c++11
// Singleton.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <mutex> #include & ...
- python的单例模式--解决多线程的单例模式失效
单例模式 单例模式(Singleton Pattern) 是一种常用的软件设计模式,主要目的是确保某一个类只有一个实例存在.希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场 比如,某个 ...
- java多线程环境单例模式实现详解
Abstract 在开发中,如果某个实例的创建需要消耗很多系统资源,那么我们通常会使用惰性加载机制,也就是说只有当使用到这个实例的时候才会创建这个实例,这个好处在单例模式中得到了广泛应用.这个机制在s ...
- java设计模式_单例模式
懒汉式 非线程安全 特点:Lazy 初始化.非多线程安全.易实现 描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程.因为没有加锁 synchronized,所以严格意义上它并不算单 ...
- java 多线程,单例模式类(创建对象)最优写法
单例模式 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式. 这种模式涉及到一个单一的类,该类负责创 ...
随机推荐
- (转)java中/r与/n还有/r/n的区别
场景:在学习spring实战,以及工作中进行写文件时候,经常遇到java中的换行问题,发现有的书上的介绍和实际使用并不一致.现在才算是搞清楚! 通过文本对比,断言应该返回正确的结果!但是事与愿违,最终 ...
- (转)Spring boot——logback.xml 配置详解(三)<appender>
文章转载自:http://aub.iteye.com/blog/1101260,在此对作者的辛苦表示感谢! 1 appender <appender>是<configuration& ...
- HDU 6034---Balala Power!(搜索+贪心)
题目链接 Problem Description Talented Mr.Tang has n strings consisting of only lower case characters. He ...
- app耗电优化之四 使用AlarmManager对任务进行合理安排
AlarmManager 是用来设定定时任务.即用来设定那个任务在什么时候开始执行.为什么和省电有关系?这个需要和AlarmManager的使用先说起.AlarmManager 实际上只起到一个定时发 ...
- 【HTML】谈谈html的meta标签
一.定义&用法 <meta> 元素可提供有关页面的元信息(meta-information),比如针对搜索引擎和更新频度的描述和关键词. <meta> 标签位于文档的头 ...
- JStorm与Storm源码分析(三)--Scheduler,调度器
Scheduler作为Storm的调度器,负责为Topology分配可用资源. Storm提供了IScheduler接口,用户可以通过实现该接口来自定义Scheduler. 其定义如下: public ...
- hive配置过程中出现的一个问题
执行hive里面的insert语句的时候,报错,执行失败查看hadoop的日志文件之后发现错误的详细信息如下: 把hdfs-site.xml中的hadoop.tmp.dir这个属性添加到core-si ...
- C#中的数据类型转换
数据类型在一定的条件下是可以相互转换的,如将int型数据转换成double型数据.C#允许使用两种转换方式:隐式转换和显式转换. 1.隐式转换 隐式转换:从类型A到类型B的转换可以在所有情况下进行,执 ...
- 设计模式笔记——GoF设计模式汇总
目录 · 总述 · 记忆 · 效果 · 面向对象设计原则 · 创建型模式 · 单例模式(Singleton) · 效果 · 分类 · 代码(饿汉式) · 代码(懒汉式) · 代码(双重检测锁式) · ...
- JS实现添加至购物车功能
效果图展示: 当将书拖拽至购物车一览时: 首先将页面的基本结构写出来: <!DOCTYPE html> <html lang="en"> <head& ...