关键词:
多线程编程 之java内存模型(JMM)与可见性问题
前言
在java内存模型中,对多线程间交互,涉及到原子性问题、可见性问题、以及有序性问题;
这篇文章主要讲解的是多线程高并发的原子性问题,以及解决原子性问题、CAS机制、自旋锁的优缺点、以及ABA问题等解决
什么是原子操作
定义
即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
通过下面代码来看出来
public class Counter
volatile int i = 0;
public void add()
i = i + +;
public class Demo1_CounterTest
public static void main(String[] args) throws InterruptedException
final Counter ct = new Counter();
CountDownLatch cl = new CountDownLatch(6);
for (int i = 0; i < 6; i++)
new Thread(new Runnable()
@Override
public void run()
for (int j = 0; j < 10000; j++)
ct.add();
cl.countDown();
System.out.println("done...");
).start();
cl.await();
System.out.println(ct.i);
上面得到的结果完全不可控,得到了下面的打印结果;明显产生了冲突,在预想的时候应该产生60000,但只得到了34684;多执行一次结果值又不一样
done...
done...
done...
done...
done...
done...
34684
产生情况的原因,为什么会出现值减少的情况
- i++不是原子操作,这在多线程操作时会出现非原子性行为
- 这是在每个线程中的私有方法内存中进行操作,就可能导致冲突,出现非原子性行为;但不是可见性问题,因此我们写入内存时,其他线程一定能看到,通过 volatile 已经发生happens-before原则
结论:
public synchronized void add()
i = i + 1;
竞态条件与临界区
竞态条件:两个线程竞争同一个资源时,如果对资源的访问顺序敏感,就称存在竞态条件。
临界区:就是导致静态条件发生的代码区。 比如上图的 i++;代码
CAS(Compare and swap) 机制
public class CounterAtomic
AtomicInteger at = new AtomicInteger(0);
public void add()
at.getAndIncrement();
public int getValue()
return at.get();
这里用到的AtomicInteger 就是利用的cas机制来达到原子性的
-
CAS 属于 硬件 同步原语,处理器提供的内存操作指令, 保证原子性 。
- CAS操作需要两个参数,一个旧值和目标值,修改前比较旧值是否改变,如果没变,将新值赋给变量,否则则不做改变。
如下图,去对比值,如果没变就赋值为111
如果两个线程同时进行操作,对于内存来说,其实还是分为两次操作,它保证同一时刻只能有一个进行修改
修改值之前进行检查,如果值是正常,就修改成功,修改失败就在更新老值进行重试。
CAS 属于硬件同步原语,处理器提供的内存操作指令,而jvm虚拟机提供给一个可以使用的类unsafe;
JAVA中的sun.misc.Unsafe类提供了CAS机制。
例如AtomicInteger中使用到的
// 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;
这个可以在自己仿造着AtomicInteger 写一个unsafe的原子操作类
static
unsafe = Unsafe.getUnsafe();
try
valueOffset = unsafe.objectFieldOffset(CountUnsafe.class.getDeclaredField("value"));
catch (Exception ex)
throw new Error(ex);
在写到上面代码时,出现了安全异常,而automicInteger中则没有异常,jdk不允许我们拿。然后就使用反射去解决
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.SecurityException: Unsafe
at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)
at org.cao.learn.CountUnsafe.<clinit>(CountUnsafe.java:12)
- 按照automicinteger可以写一个,先定义属性
private static final Unsafe unsafe;
private static final long valueOffset;
private volatile int value = 0;
- 定义好静态方法块
static
// unsafe = Unsafe.getUnsafe();
try
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
valueOffset = unsafe.objectFieldOffset(CountUnsafe.class.getDeclaredField("value"));
catch (Exception ex)
throw new Error(ex);
- 然后总的代码
package org.cao.learn;
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class CountUnsafe
private static final Unsafe unsafe;
private static final long valueOffset;
private volatile int value = 0;
static
// unsafe = Unsafe.getUnsafe();
try
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
valueOffset = unsafe.objectFieldOffset(CountUnsafe.class.getDeclaredField("value"));
catch (Exception ex)
throw new Error(ex);
public final int getAndIncrement()
return unsafe.getAndAddInt(this, valueOffset, 1);
public void add()
// i++;
for (;;)
if (unsafe.compareAndSwapInt(this, valueOffset, value, value + 1))
return;
public int getValue()
return value;
这里的getAndIncrement方法和add方法是一样的,最好是使用getAndAddInt 方法 这是native方法做了内循环,并做了优化的.
J.U.C包内的原子操作封装类
- AtomicBoolean:原子更新布尔类型
- AtomicInteger:原子更新整型
-
AtomicLong : 原子更新长整型
-
AtomicIntegerArray : 原子更新整型数组里的元素。
public AtomicIntegerArray(int[] array)
// Visibility guaranteed by final field guarantees
this.array = array.clone();
底层存的int类型,需要对其中一个元素做原子性操作
public final int getAndSet(int i, int newValue)
return unsafe.getAndSetInt(array, checkedByteOffset(i), newValue);
-
AtomicLongArray : 原子更新长整型数组里的元素。
- AtomicReferenceArray:原子更新引用类型数组里的元素。
- AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
这里做的引用就是修改对象的某个值,是它为原子操作。
ublic class Demo2_AtomicIntegerFieldUpdater
// 新建AtomicIntegerFieldUpdater对象,需要指明是哪个类中的哪个字段
private static AtomicIntegerFieldUpdater<User> atom =
AtomicIntegerFieldUpdater.newUpdater(User.class, "id");
public static void main(String[] args)
User user = new User(100, 100,"Kody");
atom.addAndGet(user, 50);
System.out.println("addAndGet(user, 50) 调用后值变为:" + user);
class User
volatile int id;
volatile int age;
private String name;
public User(int id, int age, String name)
this.id = id;
this.age = age;
this.name = name;
public String toString()
return "id:" + id + " " + "age:" + age;
- AtomicLongFieldUpdater:原子更新长整型字段的更新器。
- AtomicReferenceFieldUpdater:原子更新引用类型里的字段
- AtomicReference:原子更新引用类型。
这里也是对比对象应用,包括下面的锁机制的一部分抢锁时,对比的是锁引用是否相等。
public boolean tryLock()
//如果锁没有占用 存在原子性问题
return owner.compareAndSet(null, Thread.currentThread());
- AtomicStampedReference:原子更新带有版本号的引用类型
- AtomicMarkableReference:原子更新带有标记位的引用类型。
public long testLongAdder() throws InterruptedException
LongAdder lacount = new LongAdder();
for (int i = 0; i < 3; i++)
new Thread(() ->
long starttime = System.currentTimeMillis();
while (System.currentTimeMillis() - starttime < 2000) // 运行两秒
lacount.increment();
long endtime = System.currentTimeMillis();
).start();
Thread.sleep(3000);
return lacount.sum();
public class Demo3_LongAccumulator
public static void main(String[] args) throws InterruptedException
LongAccumulator accumulator = new LongAccumulator(
(x,y)->
System.out.println("x:" + x);
System.out.println("y:" + y);
return x+y;
,
0L);
for (int i = 0; i < 3; i++)
accumulator.accumulate(1);
System.out.println(accumulator.get());
CAS存在的问题
- 仅针对单个变量的操作,不能用于多个变量来实现原子操作
- 循环+CAS,自旋的实现让所有线程都处于高频运行,争抢CPU执行时间的状态。如果操作长时间不成功,会消耗大量的cpu资源。
- ABA问题,无法体现出数据的变动。
主要是多线程操作时,其中一个线程操作过后,得到的新值
上述的方式,在automicinteger里面不出问题,在AtomicReference中可能出现问题,也是链表结构的情况
public class Stack
// top cas无锁修改
AtomicReference<Node> top = new AtomicReference<Node>();
public void push(Node node) // 入栈
Node oldTop;
do
oldTop = top.get();
node.next = oldTop;
while (!top.compareAndSet(oldTop, node)); // CAS 替换栈顶
// 出栈
public Node pop(int time)
Node newTop;
Node oldTop;
do
oldTop = top.get();
if (oldTop == null) //如果没有值,就返回null
return null;
newTop = oldTop.next;
if (time != 0)
LockSupport.parkNanos(1000 * 1000 * time);
while (!top.compareAndSet(oldTop, newTop)); //将下一个节点设置为top
return oldTop; //将旧的Top作为值返回
测试方法
public static void main(String[] args) throws InterruptedException
Stack stack = new Stack();
//ConcurrentStack stack = new ConcurrentStack();
stack.push(new Node("B"));
stack.push(new Node("A"));
Thread thread1 = new Thread(() ->
Node node = stack.pop(800);
System.out.println(Thread.currentThread().getName() +" "+ node.toString());
System.out.println("done...");
);
thread1.start();
Thread thread2 = new Thread(() ->
LockSupport.parkNanos(1000 * 1000 * 300L);
Node nodeA = stack.pop(0);
System.out.println(Thread.currentThread().getName() +" "+ nodeA.toString());
Node nodeB = stack.pop(0); //取出B,之后B处于游离状态
System.out.println(Thread.currentThread().getName() +" "+ nodeB.toString());
stack.push(new Node("D"));
stack.push(new Node("C"));
stack.push(nodeA);
System.out.println("done...");
);
thread2.start();
LockSupport.parkNanos(1000 * 1000 * 1000 * 2L);
System.out.println("开始遍历Stack:");
Node node = null;
while ((node = stack.pop(0))!=null)
System.out.println(node.value);
线程1在拿到 老的top值,和新的node值时过后,进行cas比较并赋值的情况等待状态了;然后线程2中间插入了很多数据,又插回了原来那个对象,因此出现了ABA问题。线程2put进去的数据都变不在了,其实里面属性已经变了
解决ABA问题的办法
主要是我们不能将top引用作为对比的对象,如果按照引用去对比,属性修改,数据肯定不正确了。
AtomicStampedReference<Node> top =
new AtomicStampedReference<>(null, 0);
AtomicStampedReference添加了版本号,不只是对比引用,加入了版本号维护;
package org.cao.learn;
import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.concurrent.locks.LockSupport;
// 实现一个 栈(后进先出)
public class Stack
// top cas无锁修改
AtomicStampedReference<Node> top = new AtomicStampedReference<Node>(null, 0);
// AtomicReference<Node> top = new AtomicReference<Node>();
public void push(Node node) // 入栈
Node oldTop;
int v = 0;
do
v = top.getStamp();
oldTop = top.getReference();
node.next = oldTop;
while (!top.compareAndSet(oldTop, node, v, v + 1)); // CAS 替换栈顶
// 出栈 -- 取出栈顶 ,为了演示ABA效果, 增加一个CAS操作的延时
public Node pop(int time)
Node newTop;
Node oldTop;
int v = 0;
do
oldTop = top.getReference();
v = top.getStamp();
if (oldTop == null) // 如果没有值,就返回null
return null;
newTop = oldTop.next;
if (time != 0) // 模拟延时
LockSupport.parkNanos(1000 * 1000 * time);
while (!top.compareAndSet(oldTop, newTop, v, v + 1));
return oldTop;
多线程cas机制解析及应用(原子类.自旋锁)解决aba问题(代码片段)
@TOCCAS(Compareandswap)1、解析CASCAS:全称Compareandswap,字面意思:”比较并交换“,一个CAS涉及到以下操作:CAS伪代码:下面写的代码不是原子的,真实的CAS是一个原子的硬件指令完成的.这个伪代码只是辅助理解CAS的工作流程.booleanCAS... 查看详情
多线程之cas
...同步的,这会导致有锁锁机制存在以下问题:(1)在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。(2)一个线程持有锁会导致其它所有需要此锁的线程挂起。(3)如果一个优先级高的线... 查看详情
java并发之cas详解(代码片段)
前言在高并发的应用当中,最关键的问题就是对共享变量的安全访问,通常我们都是通过加锁的方式,比如说synchronized、Lock来保证原子性,或者在某些应用当中,用voliate来保证变量的可见性,还有就是通过TheadLocal将变量copy一... 查看详情
并发编程(学习笔记-共享模型之无锁)-part5(代码片段)
文章目录并发编程-5-共享模型之无锁1.无锁解决线程安全问题2.CAS与volatile2-1CAS2-2volatile2-3为什么无锁效率高2-4CAS特点3.原子整数4.原子引用4-1原子引用的使用4-2ABA问题及解决5.原子数组6.字段更新器7.原子累加器8.LongAdder详解8-1cas锁8... 查看详情
java——多线程高并发系列之理解cas原子变量类的使用(代码片段)
文章目录:1.CAS2.原子变量类2.1AtomicInteger2.2AtomicLong2.3AtomicIntegerArray2.4AtomicIntegerFieldUpdater2.5AtomicReference2.6AtomicStampedReference1.CASCAS(CompareAndSwap)是由硬件实现的。CAS可以将read-modify-write 查看详情
java并发编程实战之互斥锁(代码片段)
文章目录Java并发编程实战之互斥锁如何解决原子性问题?锁模型Javasynchronized关键字Javasynchronized关键字只能解决原子性问题?如何正确使用Javasynchronized关键字?锁和受保护资源的合理关联关系死锁预防死锁破坏占有... 查看详情
java并发编程实战之互斥锁(代码片段)
文章目录Java并发编程实战之互斥锁如何解决原子性问题?锁模型Javasynchronized关键字Javasynchronized关键字只能解决原子性问题?如何正确使用Javasynchronized关键字?锁和受保护资源的合理关联关系死锁预防死锁破坏占有... 查看详情
java程序中怎么保证多线程的运行安全?
并发编程三要素(线程的安全性问题体现在):原子性:原子,即一个不可再被分割的颗粒。原子性指的是一个或多个操作要么全部执行成功要么全部执行失败。可见性:一个线程对共享变量的修改,另一个线程能够立刻看到。... 查看详情
java并发编程线程锁机制(悲观锁|乐观锁|cas三大问题|aba问题|循环时间长问题|多个共享变量原子性问题)(代码片段)
文章目录一、悲观锁二、乐观锁三、乐观锁CAS三大问题一、悲观锁假设有222个线程,线程A和线程B;线程A访问共享资源,线程B等待,一旦线程A访问结束,线程B访问该共享资源;悲观锁:只要有线程来操作共享资源,就认为肯定有其它若干... 查看详情
多线程&高并发深入理解jmm产生的三大问题原子性可见性有序性
【多线程&高并发】深入浅出原子性https://blog.csdn.net/Kevinnsm/article/details/121681785?spm=1001.2014.3001.5501【多线程&高并发】深入浅出可见性https://blog.csdn.net/Kevinnsm/article/details/121683823?spm=1001.2014.3001.5501【多线程&高并发】jcs... 查看详情
java核心-多线程-并发原子类
????使用锁能解决并发时线程安全性,但锁的代价比较大,而且降低性能。有些时候可以使用原子类(juc-atomic包中的原子类)。还有一些其他的非加锁式并发处理方式,我写这篇文章来源于Java中有哪些无锁技术来解决并发问题的... 查看详情
提升--05---并发编程之---原子性---cas(代码片段)
并发编程之原子性从一个简单的小程序谈起:需求:new100个线程,每个线程对共有资源n,进行+1操作,每个线程执行10000次.按需求,最后共有资源n,应该是100*10000=1000000(1百万)代码1:importjava.util.concurrent.CountDownLatch;publicclassT00_00_... 查看详情
java并发多线程编程——原子类atomicinteger的aba问题及原子更新引用(代码片段)
目录一、ABA问题的概述二、ABA问题的产生代码示例三、原子引用类(AtomicReference)3.1、jdk1.8API中的原子引用类截图如下:3.2、原子引用类代码示例四、ABA问题的解决4.1、ABA问题的解决思路3.2、解决ABA问题的代码示例一... 查看详情
java开发之高并发必备篇——线程安全操作之synchronized
提到并发编程大多数第一时刻想到的就是synchronized同步锁了,synchronized也是面试中问的比较多的一个问题。在之前的文章中我们提到过线程安全的三个特性:原子性、可见性和有序性,并且说到了java中定义了一个关... 查看详情
java开发之高并发必备篇——线程安全操作之synchronized
提到并发编程大多数第一时刻想到的就是synchronized同步锁了,synchronized也是面试中问的比较多的一个问题。在之前的文章中我们提到过线程安全的三个特性:原子性、可见性和有序性,并且说到了java中定义了一个关... 查看详情
高并发编程-06-可见性-volatile
1,volatile的作用volatile是一个轻量级的线程同步机制。它的特性之一,是保证了变量在线程之间的可见性。当然,还有我们之前说的,解决指令重排的问题volatile保证了在多个线程之间是可见的,但不能保证原子性操作当一个线... 查看详情
cas,在硬件层面为并发安全保驾护航(代码片段)
文章目录一、前言二、CAS操作2.1AtomInteger类保证原子性2.2CAS三步操作2.3AtomicInteger类使用for循环+if(cas)来保证原子性三、CAS的ABA问题3.1ABA问题定义3.2ABA问题重现3.3ABA问题解决3.4代码解释ABA问题两个类处理3.4.1AtomicStampedReference类代... 查看详情
java多线程编程——对象及变量的并发访问(代码片段)
Java多线程编程——对象及变量的并发访问文章目录Java多线程编程——对象及变量的并发访问前言一、synchronized同步方法1.方法内的变量为线程安全2.实例变量非线程安全问题与解决方案3.同步synchronized在字节码指令中的原理4.多... 查看详情