java多线程cas_多线程读书笔记二(java内存模型、volatile变量、内存模型与synchronized、CAS)...

java内存模型 java中,线程之间的通信是通过

共享内存的方式,存储在堆中的实例域,静态域以及数组元素都可以在线程间通信。java内存模型控制一个线程对共享变量的改变何时对另一个线程可见。

线程间的共享变量存在主内存中,而对于每一个线程,都有一个私有的工作内存。工作内存是个虚拟的概念,涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化,总之就是指线程的本地内存。存在线程本地内存中的变量值对其他线程是不可见的。 如果线程A与线程B之间如要通信的话,必须要经历下面2个步骤,如图所示:

首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。

然后,线程B到主内存中去读取线程A之前已更新过的共享变量。

3a7b486fd04013c79cfafdb76c4162b6.png

关于volatile变量 由于java的内存模型中有工作内存和主内存之分,所以可能会有两种问题:

(1)线程可能在工作内存中更改变量的值,而没有及时写回到主内存,其他线程从主内存读取的数据仍然是老数据

(2)线程在工作内存中更改了变量的值,写回主内存了,但是其他线程之前也读取了这个变量的值,这样其他线程的工作内存中,此变量的值没有被及时更新。 为了解决这个问题,可以使用同步机制,也可以把变量声明为volatile,volatile修饰的成员变量有以下特点:

(1)每次对变量的修改,都会引起处理器缓存(工作内存)写回到主内存。

(2)一个工作内存回写到主内存会导致其他线程的处理器缓存(工作内存)无效。 基于以上两点,如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。 此外,

java虚拟机规范(jvm spec)中,规定了声明为volatile的long和double变量的get和set操作是原子的。这也说明了为什么将long和double类型的变量用volatile修饰,就可以保证对他们的赋值操作的原子性了

关于volatile变量的使用建议:多线程环境下需要共享的变量采用volatile声明;如果使用了同步块或者是常量,则没有必要使用volatile。

java内存模型与synchronized关键字 synchronized关键字强制实施一个互斥锁,使得被保护的代码块在同一时间只能有一个线程进入并执行。当然synchronized还有另外一个 方面的作用:

在线程进入synchronized块之前,会把工作存内存中的所有内容映射到主内存上,然后把工作内存清空再从主存储器上拷贝最新的值。而 在线程退出synchronized块时,同样会把工作内存中的值映射到主内存,但此时并不会清空工作内存。这样一来就可以强制其按照上面的顺序运行,以 保证线程在执行完代码块后,工作内存中的值和主内存中的值是一致的,保证了数据的一致性! 所以由synchronized修饰的set与get方法都是相当于直接对主内存进行操作,不会出现数据一致性方面的问题。

关于CAS

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前 值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”

为什么CAS可以用于同步? 例如,有一个变量i=0,Thread-1和Thread-2都对这个变量执行自增操作。 可能会出现Thread-1与Thread-2同时读取i=0到各自的工作内存中,然后各自执行+1,最后将结果赋予i。这样,虽然两个线程都对i执行了自增操作,但是最后i的值为1,而不是2。 解决这个问题使用互斥锁自然可以。但是也可以使用CAS来实现,思路如下: 自增操作可以分为两步:(1)从内存中读取这个变量的当前值(2)执行(变量=上一步取到的当前值+1)的赋值操作。

多线程情况下,自增操作出现问题的原因就是执行(2)的时候,变量在主内存中的值已经不等于上一步取到的当前值了,所以赋值时,用CompareAndSet操作代替Set操作:首先比较一下内存中这个变量的值是否等于上一步取到的当前值,如果等于,则说明可以执行+1运算,并赋值;如果不等于,则说明有其他线程在此期间更改了主内存中此变量的值,上一步取出的当前值已经失效,此时,不再执行+1运算及后续的赋值操作,而是返回主内存中此变量的最新值。

“比较并交换(CAS)”操作是原子操作,它使用平台提供的用于并发操作的硬件原语。

通过下面代码可以加深理解:

package com.jyq.multithread;

import java.util.List;

import java.util.ArrayList;

import java.util.concurrent.atomic.AtomicInteger;

import java.lang.Thread;

public class Counter {

private AtomicInteger atomicInteger = new AtomicInteger(0);

private int i = 0;

// 使用CAS实现线程安全的计数器

public void safeCount() {

//用一个for循环,如果没有计数成功的话,会一直执行这段代码,知道计数成功break为止

for (;;) {

int i = atomicInteger.get(); //读取value值,赋给i,i在线程的工作内存中

//将主内存中的值(current)与工作内存中的值i相比较,如果相等的话,说明工作内存中的i值仍然是value的最新值

//计数运算对当前i操作没有问题,将value值设为i+1,因为value是violent的,所以写的时候也就写到了主内存

boolean suc = atomicInteger.compareAndSet(i, i + 1);

if (suc) {

break;

}

}

}

// 非安全的线程计数器

public void count() {

i++;

}

public static void main(String[] args) {

final Counter cas = new Counter();

List ts = new ArrayList();

for (int j = 0; j < 100; j++) {

Thread t = new Thread(new Runnable() {

@Override

public void run() {

for (int i = 0; i < 10000; i++) {

cas.safeCount();

cas.count();

}

}

});

ts.add(t);

}

for (Thread t : ts) {

t.start();

}

// 等待所有线程执行完成

for (Thread t : ts) {

try {

t.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

System.out.println(cas.atomicInteger.get());

System.out.println(cas.i);

}

}

参考文章:

深入理解java内存模型系列文章

聊聊并发(五)原子操作的实现原理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值