首先必须声明,
在volatile出现之前,错误的DCL代码如下。在volatile出现之后,正确的DCL代码如下。代码如下:
//错误的代码
public class Singleton {
private static Singleton instance=null;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class) {
if(instance==null)
instance=new Singleton();//mark行
}
}
return instance;
}
}
//正确的代码
public class Singleton {
private volatile static Singleton instance=null;//添加了volatile修饰符
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class) {
if(instance==null)
instance=new Singleton();
}
}
return instance;
}
}
我们来剖析错误的代码:
在错误的DCL代码中,代码行mark在JVM中有2种可能的执行顺序(JVM会对一些没有依赖要求的指令重排序。
关于重排序,在下文中进行说明),分别为:
//第一种执行顺序
mem=allocate()
mem.initial()
instance=mem
//第二种执行顺序
mem=allocate()
instance=mem
mem.initial()
假设现在有2个线程a和b,线程a执行到mark行语句并按照第二种执行顺序执行完instance=mem指令。此时线程b执行getInstane方法会直接返回一个非空的instance,但是这个instance可能是未被完整创建的。这个时候,DCL就出问题了。这就是大家对DCL口诛笔伐的原因。由此可见,
错误的DCL的真正问题就在于:在没有同步的情况下读取一个共享变量,可能读到不完整的实例。也就是说,不在同步代码块中的if(instance==null)代码可能读取到不完整的instance实例。
为了解决类似DCL中出现的问题,
JAVA在JMM中(JAVA内存模型)定义了一系列Happens-Before关系。如果两个操作之间缺少Happens-Before关系,那么JVM就可以对它们任意的重排序。比如volatile变量规则规定:对volatile变量的写入操作必须在对该变量的读操作之前进行。这就是DCL在使用volatile之后变得正确的原因。
重排序:
下面的代码可能的输出结果有:<1,1><1,0><0,1><0,0>这四种,首先,两个线程的先后执行顺序不同,可能one先(结果是<0,1>),可能two先(结果是<1,0>),可能one和two交替执行(结果是<1,1>);其次,由于每个线程内部的各个操作之间不存在依赖性,因此这些操作可以重排序(结果可能为<0,0>)。可见,
内存级别的重排序会令程序变得不可预测,因此需要正确的使用同步,以使程序满足JMM要求的可见性规则。
public class Demo{
static int x=0,y=0;
static int a=0,b=0;
public static void main(String [] args) throws InterruptedException {
Thread one=new Thread(new Runnable() {
@Override
public void run() {
a=1;
x=b;
}
});
Thread two=new Thread(new Runnable() {
@Override
public void run() {
b=1;
y=a;
}
});
one.start(); two.start();
one.join(); two.join();
System.out.println("<"+x+","+y+">");
}
}
分享到:
相关推荐
Java并发编程是Java开发中的重要领域,特别是在多核处理器和分布式系统中,高效地利用并发可以极大地提升程序的性能和响应速度。以下是对标题和描述中所提及的几个知识点的详细解释: 1. **线程与并发** - **线程*...
1. **Java并发基础**:首先,笔记可能会介绍Java并发的基础概念,包括线程的创建(通过`Thread`类或实现`Runnable`接口)、线程的状态(新建、运行、阻塞、等待、死亡)以及线程的生命周期。 2. **同步机制**:Java...
同时,它讲解了Java内存模型(JMM)和volatile、synchronized关键字的作用,这些都是理解Java并发行为的关键。 其次,书中涵盖了多种设计模式,如生产者-消费者模式、读写锁模式、双检锁模式(DCL)等,这些模式有...
《Java并发编程实践》这本书深入探讨了Java平台上的并发编程技术,涵盖了从基础概念到高级策略的广泛主题。在Java编程中,并发处理是优化性能、提高系统资源利用率的关键手段,尤其是在多核处理器和分布式系统中更为...
总的来说,理解Java并发体系涉及深入学习JMM、线程通信、同步机制和内存模型等多个方面,这对于构建高性能、高并发的Java应用至关重要。开发者需要熟练掌握这些概念,并在实践中灵活运用,以确保程序的正确性和高效...
《Java并发编程》是一本深度探讨Java平台上的并发与多线程编程的权威书籍,适合对并发编程有深入了解需求的开发者阅读,特别是对于那些志在加入BAT(百度、阿里巴巴、腾讯)等顶级互联网企业的程序员来说,这本书是...
1. **并发基础**:介绍Java并发编程的基本概念,包括线程、进程、同步与互斥、死锁等。读者将了解Java中Thread类和Runnable接口的使用,以及如何创建和管理线程。 2. **线程安全**:讨论线程不安全的代码示例,以及...
7. **线程安全与可见性**:理解volatile关键字的作用,以及如何确保内存可见性和有序性,是Java并发编程中的重要概念。 8. **死锁、活锁与饥饿**:分析和预防这些并发问题对于编写健壮的并发程序至关重要。 9. **...
本教程将引导你从零基础开始,逐步掌握Java并发编程的核心概念和技术,助你在面试中脱颖而出,提升职场竞争力。 首先,我们需要理解什么是并发。并发是指在一段时间内,多个任务看似同时进行的状态。在Java中,实现...
接下来,面试中常问到的Java并发相关知识点包括: 1. **线程同步**:了解synchronized关键字,volatile变量,以及Lock接口(如ReentrantLock)的使用。 2. **并发容器**:如ConcurrentHashMap、...
Java并发编程是Java开发中的重要领域,特别是在多核处理器和分布式系统中,高效地利用并发可以极大地提升程序的性能和响应速度。这份“java-concurrency编程内部分享”压缩包包含了关于Java并发编程的一份PPT,它...
《Java并发编程实践》这本书是Java开发者深入理解并发编程的重要参考资料。并发编程是现代软件开发中的核心技能之一,尤其是在多核处理器普及后,利用并发来提高程序性能和响应速度变得至关重要。本书旨在帮助读者...
《Java_并发核心编程-中英文版》是一个深入探讨Java并发编程的重要资源,适用于那些希望在JDK 1.5及以后版本的Java环境中提升并发处理能力的开发者。并发编程是现代多核处理器环境下提高软件性能和效率的关键技术,...
《Java并发编程实践》这本书是Java开发者深入理解并发编程的重要参考。以下是对书中关键知识点的总结: 1. **线程和进程的区别** - **线程**:是程序执行的最小单位,一个进程中可以有多个线程,它们共享同一块...
- 理解Java内存模型(JMM),包括堆内存、栈内存和方法区。 - 掌握对象的生命周期,包括创建、使用、垃圾回收。 - 了解几种常见的垃圾收集器,如Serial、ParNew、CMS、G1等,以及他们的工作原理和优缺点。 - ...
《Java并发编程实践》是Java开发者深入理解和应用并发编程的必备参考书。这本书全面地介绍了Java平台上的并发编程,涵盖了理论、工具和技术,旨在帮助读者掌握编写高效、正确且可维护的多线程代码的关键技能。 并发...
"JAVA并发编程实践.zip"这个压缩包很可能包含了一系列关于如何有效利用Java进行并发编程的实际示例代码。下面将详细讨论Java并发编程中的关键知识点。 1. **线程与进程**:在Java中,线程是程序执行的最小单元,而...
7. **J.U.C(Java并发工具包)**:这部分将详细介绍`java.util.concurrent`包中的各种工具类,如`Future`、`CompletableFuture`以及`ForkJoinPool`和`RecursiveTask`,它们是Java 7引入的并发处理框架,用于并行计算...
Java多线程与并发是构建高性能、高可用系统的关键技术之一。在Java中,`synchronized`关键字用于实现线程间的同步,确保并发环境下的数据一致性。本文将深入探讨`synchronized`的工作原理、实现机制以及与其他并发...