代码示例有改动。
为什么使用 volatile 比同步代价更低? 同步的代价, 主要由其覆盖范围决定, 如果可以降低同步的覆盖范围, 则可以大幅提升程序性能。
而volatile 的覆盖范围仅仅变量级别的,因此它的同步代价很低。
volatile 原理是什么?
volatile 的语义, 其实是告诉处理器, 不要将我放入工作内存, 请直接在主存操作我。(工作内存详见 java 内存模型) 因此, 当多核或多线程在访问该变量时, 都将直接操作主存, 这从本质上, 做到了变量共享。
volatile 的有什么优势?
1, 更大的程序吞吐量
2, 更少的代码实现多线程
3, 程序的伸缩性较好
4, 比较好理解, 无需太高的学习成本
volatile 有什么劣势?
1, 容易出问题
2, 比较难设计 volatile 运算存在脏数据问题,volatile 仅仅能保证变量可见性, 无法保证原子性.
volatile 的 race condition 示例:
public class TestRaceCondition { private volatile int i = 0; public void increase() { i++; } public int getValue() { return i; } /** * 测试方法 * @param args */ public static void main(String[] args) { final TestRaceCondition c =new TestRaceCondition(); for (int i =0;i<100;i++) { new Thread("thread"+i){ public void run(){ c.increase(); } }.start(); } System.out.println("期望的值:100"); System.out.println("实际的值:"+c.getValue()); } } 输出结果:期望的值:100 实际的值:63
当多线程执行 increase 方法时, 是否能保证它的值会是线性递增的呢?
答案是否定的。原因: 这里的 increase 方法, 执行的操作是 i++, 即 i = i + 1; 针对 i = i + 1,
在多线程中的运算, 本身需要改变 i 的值. 如果, 在 i 已从内存中取到最新值, 但未与 1 进行运算, 此时其他线程已数次将运算结果赋值给 i. 则当前线程结束时, 之前的数次运算结果都将被覆盖。即执行100 次 increase, 可能结果是 < 100. 一般来说, 这种情况需要较高的压力与并发情况下, 才会出现。如示例所输出的结果一样。
如何避免这种情况? 解决以上问题的方法:
第一种方式是 操作时, 加上同步,这种方法, 无疑将大大降低程序性能, 且违背了 volatile 的初衷。
第二种方式是, 使用硬件原语(CAS), 实现非阻塞算法 从 CPU 原语上, 支持变量级别的低开销同步。CPU 原语-比较并交换(CompareAndSet),实现非阻塞算法。
什么是 CAS? cas 是现代 CPU 提供给并发程序使用的原语操作. 不同的 CPU 有不同的使用规范. 在 Intel 处理器中,比较并交换通过指令的 cmpxchg 系列实现。
PowerPC 处理器有一对名为"加载并保留"和"条件存储"的指令,它们实现相同的目地; MIPS 与 PowerPC 处理器相似,除了第一个指令称为"加载链接"。
CAS 操作包含三个操作数 —— 内存位置(V),预期原值(A)和新值(B) 什么是非阻塞算法? 一个线程的失败或挂起不应该影响其他线程的失败或挂起。
这类算法称之为非阻塞(non blocking)算法 对比阻塞算法: 如果有一类并发操作, 其中一个线程优先得到对象监视器的锁, 当其他线程到达同步 边界时, 就会被阻塞。
直到前一个线程释放掉锁后, 才可以继续竞争对象锁.(当然,这里的竞争也可是公平的, 按先来后到的次序)
CAS 原理: 我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更 改该位置,只告诉我这个位置现在的值即可.
CAS 使用示例(jdk 1.5 并发包 AtomicInteger 类分析
public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; /** * Creates a new AtomicInteger with the given initial value. * * @param initialValue the initial value */ public AtomicInteger(int initialValue) { value = initialValue; } /** * Atomically sets to the given value and returns the old value. * * @param newValue the new value * @return the previous value */ public final int getAndSet(int newValue) { for (;;) { int current = get(); if (compareAndSet(current, newValue)) return current; } } /** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. * * @param expect the expected value * @param update the new value * @return true if successful. False return indicates that * the actual value was not equal to the expected value. */ public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } /** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. * * <p>May <a href="package-summary.html#Spurious">fail spuriously</a> * and does not provide ordering guarantees, so is only rarely an * appropriate alternative to {@code compareAndSet}. * * @param expect the expected value * @param update the new value * @return true if successful. */ public final boolean weakCompareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } /** * Atomically increments by one the current value. * * @return the previous value */ public final int getAndIncrement() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } } /** * Atomically decrements by one the current value. * * @return the previous value */ public final int getAndDecrement() { for (;;) { int current = get(); int next = current - 1; if (compareAndSet(current, next)) return current; } } /** * Atomically adds the given value to the current value. * * @param delta the value to add * @return the previous value */ public final int getAndAdd(int delta) { for (;;) { int current = get(); int next = current + delta; if (compareAndSet(current, next)) return current; } } /** * Atomically increments by one the current value. * * @return the updated value */ public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } } /** * Atomically decrements by one the current value. * * @return the updated value */ public final int decrementAndGet() { for (;;) { int current = get(); int next = current - 1; if (compareAndSet(current, next)) return next; } } /** * Atomically adds the given value to the current value. * * @param delta the value to add * @return the updated value */ public final int addAndGet(int delta) { for (;;) { int current = get(); int next = current + delta; if (compareAndSet(current, next)) return next; } } //...... }
这个方法是, 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 与 Floa t, 但不包括对其的计算.浮点的计算,只能依靠同步关键字或 Lock 接口来实现了.
4, 对数组元素里的对象,符合以上特点的, 也可采用原子操作.包里提供了一些数组原 子操作类 AtomicIntegerArray, AtomicLongArray 等等.
5, 大幅度提升系统吞吐量及性能.
本文详细介绍[JAVA100 例]065,线程同步
/**<p>Title: 线程同步</p> * <p>Description: 通过使用同步锁实现对共享数据的操作</p> * <p>Copyright: Copyright (c) 2003</p> * <p>Filename: SyThreadDemo.java</p> * @version 1.0 */ /** * *<br> * 类说明:主程序 *<br> * 功能描述:构造两个线程,并启动它们 */ public class SyThreadDemo { public static void main(String[] args) { Trade ft = new Trade(); addThread tt1 = new addThread(ft, "add"); decThread tt2 = new decThread(ft, "dec"); tt1.start(); tt2.start(); } } /** * *<br> * 类说明:同步类 *<br> * 功能描述:保存共享数据, */ class Trade { private String transName; private double amount; /** * *<br> * 方法说明:更新数据 *<br> * 输入参数:String transName 操作名称 *<br> * 输入参数:double amount 资金数量 *<br> * 返回类型: */ synchronized void update(String transName, double amount) { this.transName = transName; this.amount = amount; System.out.println(this.transName + " " + this.amount); } } /** * *<br> * 类说明:添加资金 *<br> * 功能描述:单线程,调用 trade.update()方法,修改数据 */ class addThread extends Thread { private Trade ft; addThread(Trade ft, String name) { super(name); this.ft = ft; } public void run() { for (int i = 0; i < 10; i++) ft.update("add", 2000.0); } } /** * *<br> * 类说明:减少资金 *<br> * 功能描述:单线程,调用 trade.update()方法,修改数据 */ class decThread extends Thread { private Trade fd; decThread(Trade fd, String name) { super(name); this.fd = fd; } /** * *<br> * 方法说明:线程主体 *<br> * 输入参数: *<br> * 返回类型: */ public void run() { for (int i = 0; i < 10; i++) fd.update("dec", -2000.0); } }
相关推荐
标题和描述中提到的“多核多线程下java设计模式性能提升.pdf”,说明文章旨在讨论在现代大型系统中广泛使用的多核多线程环境下,如何通过改进Java设计模式来提升系统性能。这一主题对于Java开发者尤其重要,因为随着...
### JAVA编程高级-多线程编程 #### 一、多线程简介 多线程编程是一种软件技术,它允许在单个程序内并发执行多个控制流。这种技术极大地提高了程序的执行效率和响应能力,特别是在现代多核处理器环境中。本文档主要...
在现代计算机系统中,多核处理器已经成为主流,Java作为一款广泛应用的编程语言,其在多核多线程环境下的性能优化至关重要。Java设计模式在这样的背景下扮演着关键角色,能够帮助开发者有效地利用多核资源,提升软件...
总之,理解并掌握Java中的线程安全问题及其解决方案是每个Java开发者必备的技能,这不仅可以确保程序的正确性,还能有效利用多核处理器,提升系统性能。在阅读源码时,也要注意观察作者如何处理线程安全,这对于提升...
### Java线程高级使用知识点详解 #### 一、线程基础概述 - **定义与特点**:线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。Java是首个在语言级别明确支持线程特性的...
### Java线程教程知识点梳理 #### 一、教程概述 - **目标读者**: 本教程主要面向具备丰富Java基础知识但缺乏多线程编程经验的学习者。 - **学习成果**: 学习者能够掌握编写简单的多线程程序的能力,并能够理解和...
4. **java.util.concurrent** 包:这个包提供了高级并发工具,如Semaphore(信号量)、CyclicBarrier(回环栅栏)、CountDownLatch(计数器门栓)等,可以帮助开发者更灵活地控制线程同步。 四、死锁和避免策略 ...
此外,Java并发库(java.util.concurrent)提供了一些高级工具,如ExecutorService、Future、Callable等,帮助开发者更高效地管理多线程应用。 在客户端-服务器应用中,还需要考虑线程安全问题,确保在多线程环境下...
Java线程-Java内存模型是Java并发编程中的关键概念,它描述了多个线程如何共享和访问内存资源,以及如何保证数据的一致性和安全性。Java内存模型(JMM)是Java虚拟机规范的一部分,用于定义程序中各个线程对共享变量...
- **线程同步机制**:为了防止多个线程对共享资源的竞争,Java提供了多种同步机制,包括`synchronized`关键字、`volatile`关键字、`Lock`接口及其实现类`ReentrantLock`等。 #### 三、线程高级用法 - **线程间通信...
Java多线程是Java编程中的重要概念,尤其在如今的多核处理器环境下,理解并熟练掌握多线程技术对于提高程序性能和响应速度至关重要。本资料详细讲解了Java多线程的原理,并提供了丰富的实战代码,非常适合Java初学者...
在Java编程领域,多线程是一项至关重要的技术,它能够充分利用多核处理器的计算能力,提高应用程序的响应速度和并发性能。《Java多线程编程实战指南》这本书深入浅出地讲解了Java多线程的核心概念和实战技巧,分为...
在Java编程中,多线程并发是提升程序执行效率、充分利用多核处理器资源的重要手段。本文将基于"java 多线程并发实例"这个主题,深入探讨Java中的多线程并发概念及其应用。 首先,我们要了解Java中的线程。线程是...
综上所述,《深入浅出Java多线程.pdf》覆盖了Java多线程从基础知识到高级特性的各个方面,适合于想要深入理解Java多线程机制的开发人员阅读。无论是初学者还是有一定经验的开发者,都能从中获得宝贵的理论知识和实践...
- **Java与线程的关系:** - Java语言在设计之初就内置了对多线程的支持,使得开发者无需依赖底层操作系统即可轻松实现多线程编程。 - **轻量级进程:** - 线程有时被称为“轻量级进程”,因为它与进程类似,拥有...
Java线程与并发编程实践是Java开发者必备的技能之一,特别是在多核处理器和高并发应用环境中,有效地管理和利用线程能极大地提升程序的性能。本书《java线程与并发实践编程》由Jeff Friesen撰写,2017年2月出版,...
2. 对volatile变量的写操作与后续对同一个volatile变量的读操作同步。 3. 如果一个动作A与另一个动作B同步,则A发生在B之前(Happens-Before)。 4. 如果在一个线程的程序顺序中动作A发生在动作B之前,则A发生在B...
2. **线程同步与通信**:在多线程环境中,数据共享可能导致竞态条件和死锁,Java提供了多种同步机制来避免这些问题,如synchronized关键字、volatile变量、Lock接口(ReentrantLock、Semaphore等)以及wait()、...
### Java线程入门知识点详解 #### 一、Java线程基础知识概述 **1.1 什么是线程?** 线程是程序执行流的最小单元,是操作系统...以上介绍的是Java线程的基础知识,更多高级特性和最佳实践将在后续的学习中逐渐深入。