volatile, 用更低的代价替代同步
为什么使用volatile比同步代价更低?
同步的代价, 主要由其覆盖范围决定, 如果可以降低同步的覆盖范围, 则可以大幅提升程序性能.
而volatile的覆盖范围仅仅变量级别的. 因此它的同步代价很低.
volatile原理是什么?
volatile的语义, 其实是告诉处理器, 不要将我放入工作内存, 请直接在主存操作我.(工作内存详见java内存模型)
因此, 当多核或多线程在访问该变量时, 都将直接操作主存, 这从本质上, 做到了变量共享.
volatile的有什么优势?
1, 更大的程序吞吐量
2, 更少的代码实现多线程
3, 程序的伸缩性较好
4, 比较好理解, 无需太高的学习成本
volatile有什么劣势?
1, 容易出问题
2, 比较难设计
在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉。
Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机制。
synchronized
同步块大家都比较熟悉,通过 synchronized 关键字来实现,所有加上synchronized 和 块语句,在多线程访问的时候,同一时刻只能有一个线程能够用
synchronized 修饰的方法 或者 代码块。
volatile
用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile很容易被误用,用来进行原子性操作。
下面看一个例子,我们实现一个计数器,每次线程启动的时候,会调用计数器inc方法,对计数器进行加一
执行环境——jdk版本:jdk1.6.0_31 ,内存 :3G cpu:x86 2.4G
运行结果:Counter.count=995
实际运算结果每次可能都不一样,本机的结果为:运行结果:Counter.count=995,可以看出,在多线程的环境下,Counter.count并没有期望结果是1000
很多人以为,这个是多线程并发问题,只需要在变量count之前加上volatile就可以避免这个问题,那我们在修改代码看看,看看结果是不是符合我们的期望
运行结果:Counter.count=992
运行结果还是没有我们期望的1000,下面我们分析一下原因
在 java 垃圾回收整理一文中,描述了jvm运行时刻内存的分配。其中有一个内存区域是jvm虚拟机栈,每一个线程运行时都有一个线程栈,
线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存
变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,
在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。
read and load 从主存复制变量到当前工作内存
use and assign 执行代码,改变共享变量值
store and write 用工作内存数据刷新主存相关内容
其中use and assign 可以多次出现
但是这一些操作并不是原子性,也就是 在read load之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化,所以计算出来的结果会和预期不一样
对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的
例如假如线程1,线程2 在进行read,load 操作中,发现主内存中count的值都是5,那么都会加载这个最新的值
在线程1堆count进行修改之后,会write到主内存中,主内存中的count变量就会变为6
线程2由于已经进行read,load操作,在进行运算之后,也会更新主内存count的变量值为6
导致两个线程及时用volatile关键字修改之后,还是会存在并发的情况。
如何避免这种情况?
解决以上问题的方法:
一种是 操作时, 加上同步.
这种方法, 无疑将大大降低程序性能, 且违背了volatile的初衷.
第二种方式是, 使用硬件原语(CAS), 实现非阻塞算法
从CPU原语上, 支持变量级别的低开销同步.
CPU原语-比较并交换(CompareAndSet),实现非阻塞算法
什么是CAS?
cas是现代CPU提供给并发程序使用的原语操作. 不同的CPU有不同的使用规范.
在 Intel 处理器中,比较并交换通过指令的 cmpxchg 系列实现。
PowerPC 处理器有一对名为“加载并保留”和“条件存储”的指令,它们实现相同的目地;
MIPS 与 PowerPC 处理器相似,除了第一个指令称为“加载链接”。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)
什么是非阻塞算法?
一个线程的失败或挂起不应该影响其他线程的失败或挂起.这类算法称之为非阻塞(nonblocking)算法
对比阻塞算法:
如果有一类并发操作, 其中一个线程优先得到对象监视器的锁, 当其他线程到达同步边界时, 就会被阻塞.
直到前一个线程释放掉锁后, 才可以继续竞争对象锁.(当然,这里的竞争也可是公平的, 按先来后到的次序)
CAS 原理:
我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。
CAS使用示例(jdk 1.5 并发包 AtomicInteger类分析:)
这个方法是, AtomicInteger类的常用方法, 作用是, 将变量设置为指定值, 并返回设置前的值.
它利用了cpu原语compareAndSet来保障值的唯一性.
另, AtomicInteger类中, 其他的实用方法, 也是基于同样的实现方式.
比如 getAndIncrement, getAndDecrement, getAndAdd等等.
CAS语义上存在的"ABA 问题"
什么是ABA问题?
假设, 第一次读取V地址的A值, 然后通过CAS来判断V地址的值是否仍旧为A, 如果是, 就将B的值写入V地址,覆盖A值.
但是, 语义上, 有一个漏洞, 当第一次读取V的A值, 此时, 内存V的值变为B值, 然后在未执行CAS前, 又变回了A值.
此时, CAS再执行时, 会判断其正确的, 并进行赋值.
这种判断值的方式来断定内存是否被修改过, 针对某些问题, 是不适用的.
为了解决这种问题, jdk 1.5并发包提供了AtomicStampedReference
(有标记的原子引用)类, 通过控制变量值的版本来保证CAS正确性.
其实, 大部分通过值的变化来CAS, 已经够用了.
jdk1.5原子包介绍(基于volatile)
包的特色:
1, 普通原子数值类型AtomicInteger, AtomicLong提供一些原子操作的加减运算.
2, 使用了解决脏数据问题的经典模式-"比对后设定", 即 查看主存中数据是否与预期提供的值一致,如果一致,才更新.
3, 使用AtomicReference可以实现对所有对象的原子引用及赋值.包括Double与Float,
但不包括对其的计算.浮点的计算,只能依靠同步关键字或Lock接口来实现了.
4, 对数组元素里的对象,符合以上特点的, 也可采用原子操作.包里提供了一些数组原子操作类
AtomicIntegerArray, AtomicLongArray等等.
5, 大幅度提升系统吞吐量及性能.
相关推荐
《Java多线程编程实战指南》这本书深入浅出地讲解了Java多线程的核心概念和实战技巧,分为核心篇和设计模式篇,旨在帮助开发者掌握并应用多线程技术。 1. **线程基础** - **线程的创建**:Java提供了两种创建线程...
《Java线程——第三版》是一本专注于Java多线程编程的专业书籍,旨在帮助开发者深入理解和熟练掌握Java中的并发处理技术。多线程是现代软件开发中的重要概念,尤其是在服务器端应用、分布式系统以及高性能计算等领域...
这本书以清晰易懂的方式,结合丰富的图表和实例,全面解析了Java多线程开发中的关键概念、设计模式以及实践技巧。在Java编程领域,多线程是不可或缺的一部分,它能提升程序的执行效率,实现并发操作,但同时也带来了...
本教程将深入讲解Java多线程的基础知识,重点关注数组的定义方式、访问方法以及在多线程环境下的注意事项。数组是Java中最基本的数据结构之一,理解如何在多线程环境下正确地操作数组对于编写高效、安全的代码至关...
通过阅读《Java-jdk10-最新最全多线程编程实战指南-核心篇》这本书,开发者不仅可以理解Java多线程编程的基础知识,还能掌握高级并发编程技巧,从而在实际项目中实现高性能、高并发的程序设计。对于Java开发者来说,...
本教程将深入探讨Java多线程的相关概念、原理及实战技巧。 首先,我们需要理解什么是线程。线程是程序执行的基本单元,一个进程中可以包含多个线程,每个线程都拥有独立的程序计数器、系统栈以及局部变量。多线程...
本文将围绕“Java面试多线程高并发相关回家技巧”这一主题,深入探讨相关概念、原理以及面试中可能遇到的问题,帮助你做好充分准备。 一、线程基础 1. **线程的创建**:Java提供了两种创建线程的方式,一是通过实现...
本PDF文档详细讲解了Java多线程的相关概念、原理及实战技巧。 首先,多线程的基本概念是理解其运作机制的基础。线程是操作系统分配CPU时间的基本单位,一个进程可以包含多个线程。Java中,每个Java应用都至少有一个...
Java多线程编程是Java编程领域中的一个重要概念,它允许程序同时执行多个任务,从而提高系统资源的利用率和程序的响应速度。这份"java multithreaded programming.ppt"的PPT课件针对的是初学者,旨在帮助他们理解并...
Java多线程是Java编程中的重要概念,尤其在开发高效并发应用程序时不可或缺。在这个入门程序中,我们将探讨Java如何创建和管理线程,...这个入门程序是很好的起点,它将引导你逐步理解Java多线程的核心概念和实践技巧。
线程在Java编程中扮演着至关重要的...总之,本课程将引导你逐步深入Java线程的世界,从基础概念到高级技巧,帮助你构建强大的多线程编程能力。通过学习,你将能更好地应对并发编程中的挑战,提升代码的效率和稳定性。
《Java多线程设计模式》是一本专注于Java并发编程的权威指南,对于深入理解Java并发机制和提升多线程编程技能具有极高的价值。这本书详细介绍了如何在Java环境中有效地使用多线程,以及如何设计出高效、可维护的并发...
3. **多线程与并发**: - **线程状态与生命周期**:新建、运行、阻塞、等待、死亡等状态。 - **同步机制**:synchronized、volatile、Lock接口(如ReentrantLock)及其使用场景。 - **并发工具类**:如...
- **多线程与异步处理**:AsyncTask,Handler,Loader,以及现代异步处理库(如Retrofit、Coroutines)。 - **性能优化**:内存泄漏检测,电量优化,APK瘦身策略。 3. **前端知识**: - **HTML/CSS/JavaScript**...
### Java多线程知识点详解 ...掌握Java多线程的基本原理和使用技巧对于任何Java程序员来说都是非常必要的。通过学习本教程,你应该能够编写简单的多线程程序,并具备阅读和理解使用多线程的程序的能力。
在Java面试中,深入理解和应用Java代码与编程技巧是至关重要的。这不仅涉及到语言的基础,还包括高级特性、设计模式以及优化策略。以下是一些可能在"java面试题 - JAVA代码与编程(2)"中涉及的关键知识点: 1. **...
四、Java多线程 1. `Runnable`接口和`Thread`类:Java中创建线程的两种方式,前者适用于不继承`Thread`的情况。 2. `synchronized`关键字:用于保证线程安全,提供方法和代码块级别的互斥。 3. `volatile`关键字:...
总的来说,Java多线程、设计模式以及相关的工具和技巧是每个Java开发者必备的知识,它们在提高程序效率和稳定性方面发挥着重要作用。通过深入学习和实践,开发者可以编写出更加高效、可靠的多线程应用程序。
在Java编程领域,多线程和并发处理是不可或缺的一部分,特别是在设计高性能、高并发的应用时。这份"Java并发实践-学习笔记"涵盖了这个关键主题,旨在帮助开发者深入理解和掌握Java中的并发机制。以下是对这份笔记...
文档中可能包含了在多线程环境下遇到的挑战和解决策略。 最后,Java的输入输出(I/O)系统、网络编程、反射、注解以及最近的Java新特性(如Lambda表达式、Stream API等)也是学习的重点。这些内容可能在文档中有所...