先来看一段简单的代码,稍微有点并发知识的都可以知道打印出结果必然是一个小于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. 在Matlab中的tick可以调整方向

    需要将axis对话框的More property打开,修改TickDir,可从In改成Out.

  2. lower_bound( )和upper_bound( )怎么用嘞↓↓↓

    lower_bound( )和upper_bound( )都是利用二分查找的方法在一个排好序的数组中进行查找的. 在从小到大的排序数组中, lower_bound( begin,end,num):从数 ...

  3. mysql 选出前五个元素

    mysql> select * from test; +----+----------+-------+-----------+ | id | name | score | subject | ...

  4. MySQL的ROUND函数

    ROUND(X) ROUND(X,D) 返回参数X, 其值接近于最近似的整数.在有两个参数的情况下,返回 X ,其值保留到小数点后D位,而第D位的保留方式为四舍五入.若要接保留X值小数点左边的D 位, ...

  5. 大名鼎鼎的RPC和MQ到底有啥区别和联系

    RPC(Remote Procedure Call)远程过程调用,主要解决远程通信间的问题,不需要了解底层网络的通信机制. RPC框架 知名度较高的有Thrift(FB的).dubbo(阿里的). R ...

  6. Beta3冲刺

    队名:福大帮 组长博客链接: 作业博客 : https://edu.cnblogs.com/campus/fzu/SoftwareEngineeringClassAofFuzhouUniversity ...

  7. [转]无法解析的外部符号 _main,该符号在函数 ___tmainCRTStartup 中被引用

    刚学WinAPI编译遇到不少问题,LNK2019: 无法解析的外部符号 _main,该符号在函数 ___tmainCRTStartup 中被引用 MSVCRTD.lib test. 这个问题表明你新建 ...

  8. Netty对WebSocket的支持

    WebSocket长连接 一.创建服务端代码 1.MyServer 类 public class MyServer { public static void main(String[] args) t ...

  9. Python中为什么没有++和–(自增/减)(转)

    原文地址:http://blog.csdn.net/guang09080908/article/details/47273775(侵删) 这两天看了一些网上各大互联网公司的面试题,发现腾讯特别喜欢考察 ...

  10. apt与apt-get命令的区别

    apt 和 apt-get的区别 Debian 作为 Ubuntu.Linux Mint 和 elementary OS 等 Linux 操作系统的母板,其具有强健的「包管理」系统,它的每个组件和应用 ...