java并发编程之原子操作
先来看一段简单的代码,稍微有点并发知识的都可以知道打印出结果必然是一个小于20000的值
package com.example.test.cas; import java.io.IOException; /**
* @author hehang on 2019-10-09
* @description
*/
public class LockDemo { private volatile int i; public void add(){
i++;
} public static void main(String[] args) throws IOException { LockDemo lockDemo = new LockDemo();
for (int i = 0; i <2 ; i++) {
new Thread(() ->{
for (int j = 0; j <10000 ; j++) {
lockDemo.add();
}
}).start();
}
System.in.read();
System.out.println(lockDemo.i);
}
}
改进一下,使用jdk给我们提供的原子操作类,达到了我们预想的结果
package com.example.test.cas; import java.util.concurrent.atomic.AtomicInteger; /**
* @author hehang on 2019-10-14
* @description
*/
public class AtomicTest {
public static void main(String[] args) throws InterruptedException {
// 自增
AtomicInteger atomicInteger = new AtomicInteger(0);
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 10000; j++) {
atomicInteger.incrementAndGet();
}
}).start();
}
Thread.sleep(2000L);
System.out.println(atomicInteger.get());
}
}
下面就来探究下jdk为我们提供的原子操作类的原理,基于java native方法实现一个自己原子操作类
package com.example.test.cas; import sun.misc.Unsafe; import java.io.IOException;
import java.lang.reflect.Field; /**
* @author hehang on 2019-10-09
* @description
*/
public class LockCASDemo { private volatile int i; private static Unsafe unsafe; private static long offset;
static{
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe = (Unsafe) theUnsafe.get(null);
offset = unsafe.objectFieldOffset(LockCASDemo.class.getDeclaredField("i"));
} catch (Exception e) {
e.printStackTrace();
} } public void add(){
int curent;
int value;
do{
curent = unsafe.getIntVolatile(this,offset);
value = curent+1;
}while (!unsafe.compareAndSwapInt(this,offset,curent,value));
} public static void main(String[] args) throws IOException { LockCASDemo lockDemo = new LockCASDemo();
for (int i = 0; i <2 ; i++) {
new Thread(() ->{
for (int j = 0; j <10000 ; j++) {
lockDemo.add();
}
}).start();
}
System.in.read();
System.out.println(lockDemo.i);
}
}
实现这样一个类的要点有:1、基于反射机制获取UnSafe对象2、利用UnSafe对象获取属性偏移量,然后调用compareAndSwapInt方法,比较和替换是硬件同步原语,处理器提供了基于内存操作的原子性保证。
以上的代码未免麻烦,因此jdk为我们封装了一些原子操作类来简化使用,打开这些原子操作类的源代码,可以发现其内部实现就是循环+调用native方法(比较替换),常用的原子操作类如下:
cas存在的三个问题
1、循环+cas,自旋的实现让cpu处于高频运行状态,争抢cpu执行时间,如果并发太高,部分线程长时间执行不成功,带来很大的cpu消耗
2、只能针对单个变量实现原子操作
3、出现ABA问题
针对第一个问题,jdk1.8为我们提供了增强版的计数器,内部利用分而治之的思想来减少线程间的cpu争抢,提高并发效率,具体可以看下面的测试
package com.example.test.cas; import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder; /**
* @author hehang on 2019-10-14
* @description asd
*/
public class LongAdderDemo {
private long count = 0; // 同步代码块的方式
public void testSync() throws InterruptedException {
for (int i = 0; i < 3; i++) {
new Thread(() -> {
long starttime = System.currentTimeMillis();
while (System.currentTimeMillis() - starttime < 2000) { // 运行两秒
synchronized (this) {
++count;
}
}
long endtime = System.currentTimeMillis();
System.out.println("SyncThread spend:" + (endtime - starttime) + "ms" + " v" + count);
}).start();
}
} // Atomic方式
private AtomicLong acount = new AtomicLong(0L); public void testAtomic() throws InterruptedException {
for (int i = 0; i < 3; i++) {
new Thread(() -> {
long starttime = System.currentTimeMillis();
while (System.currentTimeMillis() - starttime < 2000) { // 运行两秒
acount.incrementAndGet(); // acount++;
}
long endtime = System.currentTimeMillis();
System.out.println("AtomicThread spend:" + (endtime - starttime) + "ms" + " v-" + acount.incrementAndGet());
}).start();
}
} // LongAdder 方式
private LongAdder lacount = new LongAdder();
public void testLongAdder() throws InterruptedException {
for (int i = 0; i < 3; i++) {
new Thread(() -> {
long starttime = System.currentTimeMillis();
while (System.currentTimeMillis() - starttime < 2000) { // 运行两秒
lacount.increment();
}
long endtime = System.currentTimeMillis();
System.out.println("LongAdderThread spend:" + (endtime - starttime) + "ms" + " v-" + lacount.sum());
}).start();
}
} public static void main(String[] args) throws InterruptedException {
LongAdderDemo demo = new LongAdderDemo();
demo.testSync();
demo.testAtomic();
demo.testLongAdder();
}
}
三种方式在同样的时间内,自增的数值如下,可以看到LongAdder的效率更高一些
SyncThread spend:2000ms v23074332
SyncThread spend:2000ms v23094924
AtomicThread spend:2000ms v-38137398
AtomicThread spend:2000ms v-38152694
SyncThread spend:2011ms v23094924
AtomicThread spend:2000ms v-38416095
LongAdderThread spend:2000ms v-40097562
LongAdderThread spend:2000ms v-40606405
LongAdderThread spend:2001ms v-40917467 Process finished with exit code 0
针对第二个问题,我们只能通过加锁或者其它手段其处理,这里不做展开
针对第三个问题,我们先模拟出以下的场景来展示这个问题
package com.example.test.cas.aba; /**
* @author hehang on 2019-10-14
* @description
*/
public class Node {
public final String item;
public Node next; public Node(String item) {
this.item = item;
} @Override
public String toString() {
return "item内容:" + this.item;
}
}
package com.example.test.cas.aba; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference; /**
* @author hehang on 2019-10-14
* @description
*/
// 实现一个 栈(后进先出)
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 替换栈顶
} // 为了演示ABA效果, 增加一个CAS操作的延时
public Node pop(int time) throws InterruptedException { // 出栈 -- 取出栈顶 Node newTop;
Node oldTop;
do {
oldTop = top.get();
if (oldTop == null) {
return null;
}
newTop = oldTop.next;
if (time != 0) {
System.out.println(Thread.currentThread() + " 休眠前拿到的数据" + oldTop.item);
TimeUnit.SECONDS.sleep(time); // 休眠指定的时间
}
}
while (!top.compareAndSet(oldTop, newTop));
return oldTop;
}
}
package com.example.test.cas.aba; /**
* @author hehang on 2019-10-14
* @description
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
Stack stack = new Stack();
stack.push(new Node("B"));
stack.push(new Node("A")); Thread thread1 = new Thread(() -> {
try {
System.out.println(Thread.currentThread() + " 拿到数据:" + stack.pop(3));
// 再继续拿,就会有问题了,理想情况stack出数据应该是 A->C->D->B,实际上ABA问题导致A-B->null
System.out.println(Thread.currentThread() + " 拿到数据:" + stack.pop(0));
System.out.println(Thread.currentThread() + " 拿到数据:" + stack.pop(0));
System.out.println(Thread.currentThread() + " 拿到数据:" + stack.pop(0));
} catch (Exception e) {
e.printStackTrace();
}
});
thread1.start(); Thread.sleep(300); // 让线程1先启动 Thread thread2 = new Thread(() -> {
Node A = null;
try {
A = stack.pop(0);
System.out.println(Thread.currentThread() + " 拿到数据:" + A);
stack.push(new Node("D"));
stack.push(new Node("C"));
stack.push(A);
} catch (Exception e) {
e.printStackTrace();
}
});
thread2.start();
}
}
在上面的例子中我们想实现一个栈,单线程情况下是没有任何问题的,但是在并发场景下就会出现丢数据的问题,运行结果:
Thread[Thread-0,5,main] 睡一下,预期拿到的数据A
Thread[Thread-1,5,main] 拿到数据:item内容:A
Thread[Thread-0,5,main] 拿到数据:item内容:A
Thread[Thread-0,5,main] 拿到数据:item内容:B
Thread[Thread-0,5,main] 拿到数据:null
Thread[Thread-0,5,main] 拿到数据:null Process finished with exit code 0
好在jdk为我们考虑了这个问题,提供了AtomicStampedReference和AtomicMarkableReference,前者基于时间戳,后者基于标记位来对同样的数据做区分,从未避免了ABA问题
package com.example.test.cas.aba; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference; /**
* @author hehang on 2019-10-14
* @description
*/
public class ConcurrentStack {
AtomicStampedReference<Node> top = new AtomicStampedReference<Node>(null,0);
public void push(Node node){
Node oldTop;
int v;
do{
v=top.getStamp();
oldTop = top.getReference();
node.next = oldTop;
}
while(!top.compareAndSet(oldTop, node,v,v+1));
// }while(!top.compareAndSet(oldTop, node,top.getStamp(),top.getStamp()+1));
}
public Node pop(int time){
Node newTop;
Node oldTop;
int v;
do{
v=top.getStamp();
oldTop = top.getReference();
if(oldTop == null){
return null;
}
newTop = oldTop.next;
try {
if (time != 0) {
System.out.println(Thread.currentThread() + " 睡一下,预期拿到的数据" + oldTop.item);
TimeUnit.SECONDS.sleep(time); // 休眠指定的时间
}
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
while(!top.compareAndSet(oldTop, newTop,v,v+1));
// }while(!top.compareAndSet(oldTop, newTop,top.getStamp(),top.getStamp()));
return oldTop;
}
public void get(){
Node node = top.getReference();
while(node!=null){
System.out.println(node.item);
node = node.next;
}
}
}
package com.example.test.cas.aba; /**
* @author hehang on 2019-10-14
* @description
*/
public class Test2 {
public static void main(String[] args) throws InterruptedException {
ConcurrentStack stack = new ConcurrentStack();
stack.push(new Node("B"));
stack.push(new Node("A")); Thread thread1 = new Thread(() -> {
try {
System.out.println(Thread.currentThread() + " 拿到数据:" + stack.pop(3));
// #再继续拿,就会有问题了,理想情况stack出数据应该是 A->C->D->B,实际上ABA问题导致A-B->null
System.out.println(Thread.currentThread() + " 拿到数据:" + stack.pop(0));
System.out.println(Thread.currentThread() + " 拿到数据:" + stack.pop(0));
System.out.println(Thread.currentThread() + " 拿到数据:" + stack.pop(0));
} catch (Exception e) {
e.printStackTrace();
}
});
thread1.start(); Thread.sleep(300); // 让线程1先启动 Thread thread2 = new Thread(() -> {
Node A = null;
try {
A = stack.pop(0);
System.out.println(Thread.currentThread() + " 拿到数据:" + A);
stack.push(new Node("D"));
stack.push(new Node("C"));
stack.push(A);
} catch (Exception e) {
e.printStackTrace();
}
});
thread2.start();
}
}
结果如下:
Thread[Thread-0,5,main] 睡一下,预期拿到的数据A
Thread[Thread-1,5,main] 拿到数据:item内容:A
Thread[Thread-0,5,main] 睡一下,预期拿到的数据A
Thread[Thread-0,5,main] 拿到数据:item内容:A
Thread[Thread-0,5,main] 拿到数据:item内容:C
Thread[Thread-0,5,main] 拿到数据:item内容:D
Thread[Thread-0,5,main] 拿到数据:item内容:B Process finished with exit code 0
java并发编程之原子操作的更多相关文章
- Java并发编程之原子操作类
什么是原子操作类当更新一个变量的时候,多出现数据争用的时候可能出现所意想不到的情况.这时的一般策略是使用synchronized解决,因为synchronized能够保证多个线程不会同时更新该变量.然 ...
- Java并发编程系列-(3) 原子操作与CAS
3. 原子操作与CAS 3.1 原子操作 所谓原子操作是指不会被线程调度机制打断的操作:这种操作一旦开始,就一直运行到结束,中间不会有任何context switch,也就是切换到另一个线程. 为了实 ...
- java并发编程:线程安全管理类--原子操作类--AtomicInteger
在java并发编程中,会出现++,--等操作,但是这些不是原子性操作,这在线程安全上面就会出现相应的问题.因此java提供了相应类的原子性操作类. 1.AtomicInteger
- Java并发编程:volatile关键字解析
Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在 ...
- JAVA并发编程J.U.C学习总结
前言 学习了一段时间J.U.C,打算做个小结,个人感觉总结还是非常重要,要不然总感觉知识点零零散散的. 有错误也欢迎指正,大家共同进步: 另外,转载请注明链接,写篇文章不容易啊,http://www. ...
- 【Java并发编程实战】-----“J.U.C”:Semaphore
信号量Semaphore是一个控制访问多个共享资源的计数器,它本质上是一个"共享锁". Java并发提供了两种加锁模式:共享锁和独占锁.前面LZ介绍的ReentrantLock就是 ...
- 【java并发编程实战】-----线程基本概念
学习Java并发已经有一个多月了,感觉有些东西学习一会儿了就会忘记,做了一些笔记但是不系统,对于Java并发这么大的"系统",需要自己好好总结.整理才能征服它.希望同仁们一起来学习 ...
- java并发编程(2)--volatile(转)
转载:http://ifeve.com/volatile/ 作者:方 腾飞 花名清英,并发网(ifeve.com)创始人,畅销书<Java并发编程的艺术>作者,蚂蚁金服技术专家.目前工作于 ...
- Java并发编程:并发容器之ConcurrentHashMap(转载)
Java并发编程:并发容器之ConcurrentHashMap(转载) 下面这部分内容转载自: http://www.haogongju.net/art/2350374 JDK5中添加了新的concu ...
随机推荐
- PHP base_convert() 函数
16进制转8进制 <?php $hex = "E196"; echo base_convert($hex,,); ?> 8进制数转换为10进制数 <?php $o ...
- PHP strtok() 函数
我们仅在第一次调用 strtok() 函数时使用了 string 参数.在首次调用后,该函数仅需要 split 参数,这是因为它清楚自己在当前字符串中所在的位置. 如需分割一个新的字符串,请再次调用带 ...
- QML学习(一)——<简要概念知识点>
转载:https://www.cnblogs.com/dengyg0710/p/10644936.html 1.一个 QML 文档有且只有一个根元素. 2.QML 元素名后所有内容使用 {} 包围起来 ...
- OpenFOAM——具有压差且平行平板间具有相对运动流动
本算例翻译整理自:http://the-foam-house5.webnode.es/products/chapter-1-plane-parallel-plates-case/ 这个算例中两平板间具 ...
- Spring Boot 配置文件 bootstrap vs application 到底有什么区别?
用过 Spring Boot 的都知道在 Spring Boot 中有以下两种配置文件 bootstrap (.yml 或者 .properties) application (.yml 或者 .pr ...
- CTF SSTI(服务器模板注入)
目录 基础 一些姿势 1.config 2.self 3.[].() 3.url_for, g, request, namespace, lipsum, range, session, dict, g ...
- html5 css3 背景视频循环播放代码
<div style ="position: absolute; z-index: -1; top: 0px; left: 0px; bottom: 0px; right: 0px; ...
- shell | crontab 定时任务
crontab工具 linux下自带的定时任务执行器 常用命令:crontab -l //显示用户的crontab文件的内容crontab -e //编辑用户的crontab文件的内容crontab ...
- JVM探究之 —— HotSpot虚拟机对象探秘
本节以常用的虚拟机HotSpot和常用的内存区域Java堆为例,深入探讨HotSpot虚拟机在Java堆中对象分配.布局和访问的全过程. 1. 对象的创建 Java是一门面向对象的编程语言.在语言层面 ...
- v-if和v-for一起使用的几个方法
方法一(推荐): 不带if <ul> <li v-for="(item, index) in list" :key="index" > ...