立即加载:“饿汉模式”

立即加载就是指使用类的时候已经将对象创建完毕,常见的实现方法就是直接new实例化。也就是在调用方法前,实例就被创建了。示例代码如下所示:

class MyObject{

private static MyObject myObject=new MyObject();

private MyObject(){}

public static MyObject getInstance(){

//如果还有其他代码,存在线程安全问题

return myObject;

}

}

class MyThread extends Thread{

@Override

public void run(){

System.out.println(MyObject.getInstance().hashCode());

}

}

public class Run{

public static void main(String[] args){

MyThread t1=new MyThread();

MyThread t2=new MyThread();

MyThread t3=new MyThread();

t1.start();

t2.start();

t3.start();

}

}

运行结果如下:

58615885

58615885

58615885

可以发现,实现了单例模式,因为多个线程得到的实例的hashCode是一样的。

延迟加载:“懒汉模式”

延迟加载就是在调用getInstance()方法时实例才被创建,常见的方法就是在getInstance()方法中进行new实例化。实现代码如下:

class MyObject{

private static MyObject myObject;

private MyObject(){}

public static MyObject getInstance(){

if(myObject==null){ //A线程执行

myObject=new MyObject();//B线程执行

}

return myObject;

}

}

class MyThread extends Thread{

@Override

public void run(){

System.out.println(MyObject.getInstance().hashCode());

}

}

public class Run{

public static void main(String[] args){

MyThread t1=new MyThread();

MyThread t2=new MyThread();

MyThread t3=new MyThread();

t1.start();

t2.start();

t3.start();

}

}

但是由于在getInstance()中,存在多条语句,因此可能存在线程安全问题。运行结果也显示了这一点:

2041531420

1348345633

1348345633

甚至,当getInstance()中,有更多的语句,会出现不同的三个对象,在if(myObject==null)语句块中加入Thread.sleep(3000),运行结果如下所示:

218620763

58615885

712355351

DCL

如果使用synchronized关键字,对整个getInstance()上锁或者对整个if语句块加锁,会存在效率问题。

最终采用了DCL(Double-Check Locking)双检查锁机制,也是大多数多线程结合单例模式使用的解决方案。第一层主要是为了避免不必要的同步,第二层判断则是为了在null情况下才创建实例。

public class MyObject{

private static MyObject myObject;

private MyObject(){

}

public static MyObject getInstance(){

if (myObject == null) {//第一次检查

try {

Thread.sleep(3000);

} catch (InterruptedException e) {

e.printStackTrace();

}

synchronized (MyObject.class) {//加锁

if (myObject == null) {//第二次检查

myObject = new MyObject();//创建对象

}

}

}

return myObject;

}

}

测试结果,得到的是相同的hashcode。

上述双重检查锁定的方法从表面上看来达到了如下的效果:

多个线程试图在同一时间创建对象时,通过加锁保证只有一个线程能创建对象

在对象创建好之后,执行 getInstance() 方法将不需要获取锁,直接返回已经创建好的对象

创建对象可以分解为 分配内存;初始化;设置myObject指向的内存地址。但是由于重排序,可能导致先分配地址再进行初始化。因此,在线程A设置好了内存空间,还未初始化时,线程B判断不为空,将访问该对象,得到了一个还未初始化的对象。

解决办法有两种:

不允许初始化和设置变量指向的内存地址两步重排序(使用volatile)

允许这两步重排序,但是不允许其他线程看到这个重排序

基于volatile的解决方案

public class MyObject{

private volatile static MyObject myObject;

private MyObject(){

}

public static MyObject getInstance(){

if (myObject == null) {//第一次检查

synchronized (MyObject.class) {//加锁

if (myObject == null) {//第二次检查

myObject = new MyObject();//创建对象

}

}

}

return myObject;

}

}

由于instance是volatile修饰的,初始化和设置内存地址在多线程环境中将被禁止重排序。

基于类初始化的解决方案(静态内置类)

public class MyObject{

private static class MyObjectHandler{

private static MyObject myObject=new MyObject();

}

private MyObject(){

}

public static MyObject getInstance(){

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

return MyObjectHandler.myObject;

}

}

采用静态内置类的方法,是线程安全的。JVM在类的初始化阶段,会执行类的初始化。在执行类的初始化期间,JVM会去获取一个锁,可以同步多个线程对同一个类的初始化。虽然可能重排序,但是其他线程是无法看到这个过程的。

类的初始化过程:

通过在 Class 对象上同步(获取 Class 对象的初始化锁),控制类或接口的初始化。这个获取锁的线程会一直等待,直到当前线程能获得这个初始化锁。

线程A执行类的初始化,线程B在初始化锁对应的 condition 上等待。

线程A设置state=initialized,然后唤醒在 condition 中等待的所有线程。

其他线程获取到初始化锁,读取到state,释放该锁,然后类初始化处理过程完成。

使用static代码块

静态代码块的代码再使用类的时候就已经执行了,所以可以应用静态代码块的这个特性来实现单例设计模式。

public class MyObject{

private static MyObject myObject=null;

static{myObject=new MyObject();}

private MyObject(){

}

public static MyObject getInstance(){

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

return myObject;

}

}

使用enum枚举数据类型

使用枚举类时,和静态代码块的特性相似,构造方法会被自动调用。枚举在经过javac的编译之后,会被转换成形如public final class T extends Enum的定义。也就是说,我们定义的一个枚举,在第一次被真正用到的时候,会被虚拟机加载并初始化,而这个初始化过程是线程安全的。而我们知道,解决单例的并发问题,主要解决的就是初始化过程中的线程安全问题。

所以,由于枚举的以上特性,枚举实现的单例是天生线程安全的。同时,枚举可解决反序列化会破坏单例的问题。

enum MyObject{

INSTANCE;

}

SimpleDataFormat

SimpleDataFormat使用了单例模式,具有线程安全问题。SimpleDateFormat中的日期格式不是同步的。推荐(建议)为每个线程创建独立的格式实例。如果多个线程同时访问一个格式,则它必须保持外部同步。

解决方案1:需要的时候创建新实例

public class DateUtil {

public static String formatDate(Date date)throws ParseException{

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

return sdf.format(date);

}

public static Date parse(String strDate) throws ParseException{

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

return sdf.parse(strDate);

}

}

在需要用到SimpleDateFormat 的地方新建一个实例,不管什么时候,将有线程安全问题的对象由共享变为局部私有都能避免多线程问题,不过也加重了创建对象的负担。在一般情况下,这样其实对性能影响比不是很明显的。

解决方案2:同步SimpleDateFormat对象

public class DateSyncUtil{

private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

public static String formatDate(Date date)throws ParseException{

synchronized(sdf){

return sdf.format(date);

}

}

public static Date parse(String strDate) throws ParseException{

synchronized(sdf){

return sdf.parse(strDate);

}

}

}

当线程较多时,当一个线程调用该方法时,其他想要调用此方法的线程就要block,多线程并发量大的时候会对性能有一定的影响。

解决方案3:使用ThreadLocal

public class ConcurrentDateUtil{

private static ThreadLocal threadLocal = new ThreadLocal() {

@Override

protected DateFormat initialValue(){

return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

}

};

public static Date parse(String dateStr) throws ParseException{

return threadLocal.get().parse(dateStr);

}

public static String format(Date date){

return threadLocal.get().format(date);

}

}

使用ThreadLocal, 也是将共享变量变为独享,线程独享肯定能比方法独享在并发环境中能减少不少创建对象的开销。如果对性能要求比较高的情况下,一般推荐使用这种方法。

java 静态代码块 多线程,Java多线程编程笔记10:单例模式相关推荐

  1. java 静态代码块有什么用,java编程开发静态代码块的使用方法都有哪些

    代码块是程序员在学习java编程开发的时候会接触到的一个代码,而今天我们就通过案例分析来了解一下,java编程开发静态代码块的使用方法都有哪些. (一)java静态代码块静态方法区别 一般情况下,如果 ...

  2. java静态代码块和静态变量_Java中的静态变量、静态方法与静态代码块

    我们知道类的生命周期分为装载.连接.初始化.使用和卸载的五个过程. 其中静态代码在类的初始化阶段被初始化.而非静态代码则在类的使用阶段(也就是实例化一个类的时候)才会被初始化. 静态变量 可以将静态变 ...

  3. Java~~静态代码块

    1. 可以使用static关键字来定义"静态代码块": (1)语法格式: static{ java语句: } (2)静态代码块在类加载时执行,并且只执行一次. (3)静态代码块在一 ...

  4. java静态代码块和实例代码块

    一.静态代码块 1.什么是静态代码块? 用static修饰的代码块是静态代码块.它是在类加载时加载的,并且只会加载一次. 2.静态代码块的语法 static { java语句: java语句: } 3 ...

  5. java静态代码块的作用域_java基础之面向对象

    0. 面向对象与面向过程的区别 面向对象 如:指挥者,人,笔记本等都可以是对象,"万物皆对象" 面向过程 如:执行者 1. 面向对象的三个特征:封装,继承,多态. 以后的开发就是: ...

  6. java 静态代码块_关于Java你不知道的那些事之代码块

    前言 普通代码块:在方法或语句中出现的{},就被称为代码块 静态代码块:静态代码块有且仅加载一次,也就是在这个类被加载至内存的时候 普通代码块和一般语句执行顺序由他们在代码中出现的次序决定,先出现先执 ...

  7. java 静态代码块 作用域_java static关键字和代码块

    static关键字 为什么需要学习static关键字? 针对某一个变量属于类而不属于某一个具体的对象的时候,我们可以考虑使用static关键字 static概述: 多个对象共享同一份数据 static ...

  8. java静态代码块和构造方法_Java静态代码块和构造方法执行顺序

    package com.uno.staticBlock; import java.lang.reflect.Field; import java.util.Vector; /** * 验证静态代码块和 ...

  9. [转载] Java静态代码块/构造代码块/构造函数/静态变量/成员变量(相关示例)

    参考链接: Java程序的输出| 构造函数 近期做牛客网的Java开发笔试题,发现这类型的题目较多,很容易混淆,特将相关概念和相关示例整理如下,供大家参考^_^ 1. 静态代码块在类加载时即运行,而且 ...

最新文章

  1. gff文件_GFF格式说明
  2. 水仙花数java_Java三种求水仙花数的方法
  3. java单词按字典排序_最终Java日志字典:开发人员最常记录的单词是什么?
  4. 轮番滑动PHP,touch事件之滑动判断(左右上下方向)
  5. xcode 证书生成、加载
  6. Excel 时间戳和时间格式的互相转换的方法
  7. Oracle11g最佳培训高清下载版(王二暖Oracle11g教室\10年经验毫无保留)
  8. win8调出右侧菜单栏
  9. java编程_Java最热常用编程软件分享
  10. ssh和ssm的区别,好处
  11. 4am永远 鼠标按键设置_罗技的MX Master鼠标是苹果Mac系统下最好的鼠标
  12. Iphone手机,调用微信支付JSAPI缺少参数 timeStamp
  13. 计算机程序漏洞用英语怎么说,网络用语bug是什么意思,中文翻译是虫子(指电脑程序漏洞)...
  14. Visual Studio 2015创建ASP.NET5项目“DNX SDK version 'dnx-clr-win-x86.1.0.0-beta5' 无法安装的错误...
  15. 关于联想Y700,玩游戏闪屏
  16. uni-app 快手小程序如何设置跟元素样式
  17. 《神经科学:探索脑》学习笔记(第2章 神经元和神经胶质细胞)
  18. 吴恩达《深度学习》课程介绍
  19. 读取超大json文件中数据格式的方法
  20. 错排——没有不动点排列 通俗易懂!!!

热门文章

  1. JQ加AJAX 加PHP实现网页登录功能
  2. 微信小程序动态设置 tabBar
  3. 微信小程序动画无限循环 掉花
  4. 6 OC 中的isa 指针
  5. 【Runtime】动态添加方法demo
  6. 2019BATJ面试题详解:MyBatis+MySQL+Spring+Redis+多线程
  7. cnblogs不愧为cnblogs
  8. js 使用 Lawnchair 存储 json 对象到本地
  9. mysql的基本知识
  10. Java多线程编程实战:模拟大量数据同步