先来看一段简单的代码,稍微有点并发知识的都可以知道打印出结果必然是一个小于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并发编程之原子操作的更多相关文章

  1. Java并发编程之原子操作类

    什么是原子操作类当更新一个变量的时候,多出现数据争用的时候可能出现所意想不到的情况.这时的一般策略是使用synchronized解决,因为synchronized能够保证多个线程不会同时更新该变量.然 ...

  2. Java并发编程系列-(3) 原子操作与CAS

    3. 原子操作与CAS 3.1 原子操作 所谓原子操作是指不会被线程调度机制打断的操作:这种操作一旦开始,就一直运行到结束,中间不会有任何context switch,也就是切换到另一个线程. 为了实 ...

  3. java并发编程:线程安全管理类--原子操作类--AtomicInteger

    在java并发编程中,会出现++,--等操作,但是这些不是原子性操作,这在线程安全上面就会出现相应的问题.因此java提供了相应类的原子性操作类. 1.AtomicInteger

  4. Java并发编程:volatile关键字解析

    Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在 ...

  5. JAVA并发编程J.U.C学习总结

    前言 学习了一段时间J.U.C,打算做个小结,个人感觉总结还是非常重要,要不然总感觉知识点零零散散的. 有错误也欢迎指正,大家共同进步: 另外,转载请注明链接,写篇文章不容易啊,http://www. ...

  6. 【Java并发编程实战】-----“J.U.C”:Semaphore

    信号量Semaphore是一个控制访问多个共享资源的计数器,它本质上是一个"共享锁". Java并发提供了两种加锁模式:共享锁和独占锁.前面LZ介绍的ReentrantLock就是 ...

  7. 【java并发编程实战】-----线程基本概念

    学习Java并发已经有一个多月了,感觉有些东西学习一会儿了就会忘记,做了一些笔记但是不系统,对于Java并发这么大的"系统",需要自己好好总结.整理才能征服它.希望同仁们一起来学习 ...

  8. java并发编程(2)--volatile(转)

    转载:http://ifeve.com/volatile/ 作者:方 腾飞 花名清英,并发网(ifeve.com)创始人,畅销书<Java并发编程的艺术>作者,蚂蚁金服技术专家.目前工作于 ...

  9. Java并发编程:并发容器之ConcurrentHashMap(转载)

    Java并发编程:并发容器之ConcurrentHashMap(转载) 下面这部分内容转载自: http://www.haogongju.net/art/2350374 JDK5中添加了新的concu ...

随机推荐

  1. Cocos CreatorUI系统上

    若本号内容有做得不到位的地方(比如:涉及版权或其他问题),请及时联系我们进行整改即可,会在第一时间进行处理. 请点赞!因为你们的赞同/鼓励是我写作的最大动力! 欢迎关注达叔小生的简书! 这是一个有质量 ...

  2. GoCN每日新闻(2019-10-15)

    GoCN每日新闻(2019-10-15) GoCN每日新闻(2019-10-15) 1. Go Module 存在的意义与解决的问题 https://www.ardanlabs.com/blog/20 ...

  3. 【POJ2996】Help Me with the Game

    题目传送门 本题知识点:模拟(如果对国际象棋不熟悉的同学可以先百度一下) 题意很简单,就是让我们找出白棋跟黑棋每枚棋子的位置,并要按照一定的顺序输出( K -> Q -> R -> ...

  4. HttpClient介绍和简单使用流程

    HttpClient SpringCloud中服务和服务之间的调用全部是使用HttpClient,还有前面使用SolrJ中就封装了HttpClient,在调用SolrTemplate的saveBean ...

  5. D3.js的v5版本入门教程(第九章)——完整的柱状图

    D3.js的v5版本入门教程(第九章) 一个完整的柱状图应该包括的元素有——矩形.文字.坐标轴,现在,我们就来一一绘制它们,这章是前面几章的综合,这一章只有少量新的知识点,它们是 d3.scaleBa ...

  6. java 中利用反射机制获取和设置实体类的属性值

    摘要: 在java编程中,我们经常不知道传入自己方法中的实体类中到底有哪些方法,或者,我们需要根据用户传入的不同的属性来给对象设置不同的属性值,那么,java自带的反射机制可以很方便的达到这种目的,同 ...

  7. 第06组 Beta版本演示

    队名:福大帮 组长博客链接: https://www.cnblogs.com/mhq-mhq/p/12052263.html 作业博客 : https://edu.cnblogs.com/campus ...

  8. python练习:寒冰猴子狐狸,猫狗咬架

    python练习:寒冰猴子狐狸,猫狗咬架 一,寒冰猴子狐狸 class Person: def __init__(self, na, gen, age, fig): self.name = na se ...

  9. 【mybatis源码学习】mybatis的sql语句映射

    一.重要的接口和类 org.apache.ibatis.scripting.LanguageDriver //语言驱动org.apache.ibatis.scripting.xmltags.XMLLa ...

  10. VBA 宏文件源代码密码解除

    VBA Project密码解除第一种方法详细步骤参考:以下VBA代码是第二种方法 '使用本代码之前需要将需要解除密码保护的含有宏的Excel文件(如果是xlsm文件,需要先另存为97-03版的xls文 ...