利用CAS实现原子操作类AtomicInteger (这是自定义的AtomicInteger;java有封装好的原子操作AtomicInteger类):

class AtomicInteger {

    private static final Unsafe unsafe = Unsafe.getUnsafe();
//在没有锁的机制下需要volatile修饰,保证线程间数据是可见的。
public volatile int value; public final int get() {
return value;
} //自增操作
public final int incrementAndGet() {
for(; ;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
} //利用JNI来完成CPU指令的操作
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}

为什么需要AtomicInteger原子操作类?
对于Java中的运算操作,例如自增或自减,若没有进行额外的同步操作,在多线程环境下就是线程不安全的。num++解析为num=num+1,明显,这个操作不具备原子性,多线程并发共享这个变量时必然会出现问题。测试代码如下:

public class AtomicIntegerTest1 {
private static final int THREADS_CONUT = 20;
public static int count = 0; public static void increase() {
count++;
} public static void main(String[] args) {
Thread[] threads = new Thread[THREADS_CONUT];
for (int i = 0; i < THREADS_CONUT; i++) {
threads[i] = new Thread(() -> {
for (int i1 = 0; i1 < 1000; i1++) {
increase();
}
});
threads[i].start();
}
//Eclipse中返回1,大于一个是因为一般还会有一个main主线程,总不能把main主线程都搞死了还判断activeCount()
//idea中返回2:可能是idea启动一个监控线程,如下 java.lang.ThreadGroup[name=main,maxpri=10] Thread[main,5,main] Thread[Monitor Ctrl-Break,5,main]
while (Thread.activeCount() > 2) { //返回活动线程的当前线程的线程组中的数量
Thread.yield();//让出cpu资源使用权,让自己或者其它线程 重新一起竞争cpu (放大并发!!) 也就是说,当前线程调用yield()之后,并不能保证:其它具有相同优先级的线程一定能获得执行权,也有可能是当前线程又进入到“运行状态”继续运行。
}
System.out.println(count);
}
}

这里运行了20个线程,每个线程对count变量进行1000此自增操作,如果上面这段代码能够正常并发的话,最后的结果应该是20000才对,但实际结果却发现每次运行的结果都不相同,都是一个小于20000的数字。这是为什么呢?

要是换成volatile修饰count变量呢?
顺带说下volatile关键字很重要的两个特性:

1、保证变量在线程间可见,对volatile变量所有的写操作都能立即反应到其他线程中,换句话说,volatile变量在各个线程中是一致的(得益于java内存模型—"先行发生原则");

2、禁止指令的重排序优化;

那么换成volatile修饰count变量后,会有什么效果呢? 试一试:

public class AtomicIntegerTest2 {
private static final int THREADS_CONUT = 20;
public static volatile int count = 0; public static void increase() {
count++;
} public static void main(String[] args) {
Thread[] threads = new Thread[THREADS_CONUT];
for (int i = 0; i < THREADS_CONUT; i++) {
threads[i] = new Thread(() -> {
for (int i1 = 0; i1 < 1000; i1++) {
increase();
}
});
threads[i].start();
} while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(count);
}
}

结果似乎又失望了,测试结果和上面的一致,每次都是输出小于20000的数字。这又是为什么么? 上面的论据是正确的,也就是上面标红的内容,但是这个论据并不能得出"基于volatile变量的运算在并发下是安全的"这个结论,因为核心点在于java里的运算(比如自增)并不是原子性的。

用了AtomicInteger类后会变成什么样子呢?
把上面的代码改造成AtomicInteger原子类型,先看看效果

import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerTest3 {
private static final int THREADS_CONUT = 20;
public static AtomicInteger count = new AtomicInteger(0); public static void increase() {
count.incrementAndGet();
} public static void main(String[] args) {
Thread[] threads = new Thread[THREADS_CONUT];
for (int i = 0; i < THREADS_CONUT; i++) {
threads[i] = new Thread(() -> {
for (int i1 = 0; i1 < 1000; i1++) {
increase();
}
});
threads[i].start();
} while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(count);
}
}

结果每次都输出20000,程序输出了正确的结果,这都归功于AtomicInteger.incrementAndGet()方法的原子性。

非阻塞同步
同步:多线程并发访问共享数据时,保证共享数据再同一时刻只被一个或一些线程使用。

我们知道,阻塞同步和非阻塞同步都是实现线程安全的两个保障手段,非阻塞同步对于阻塞同步而言主要解决了阻塞同步中线程阻塞和唤醒带来的性能问题,那什么叫做非阻塞同步呢?在并发环境下,某个线程对共享变量先进行操作,如果没有其他线程争用共享数据那操作就成功;如果存在数据的争用冲突,那就才去补偿措施,比如不断的重试机制,直到成功为止,因为这种乐观的并发策略不需要把线程挂起,也就把这种同步操作称为非阻塞同步(操作和冲突检测具备原子性)。在硬件指令集的发展驱动下,使得 "操作和冲突检测" 这种看起来需要多次操作的行为只需要一条处理器指令便可以完成,这些指令中就包括非常著名的CAS指令(Compare-And-Swap比较并交换)。《深入理解Java虚拟机第二版.周志明》第十三章中这样描述关于CAS机制:

图取自《深入理解Java虚拟机第二版.周志明》13.2.2
所以再返回来看AtomicInteger.incrementAndGet()方法,它的时间也比较简单

    /**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}

incrementAndGet()方法在一个无限循环体内,不断尝试将一个比当前值大1的新值赋给自己,如果失败则说明在执行"获取-设置"操作的时已经被其它线程修改过了,于是便再次进入循环下一次操作,直到成功为止。这个便是AtomicInteger原子性的"诀窍"了,继续进源码看它的compareAndSet方法:

    /**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return true if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

可以看到,compareAndSet()调用的就是Unsafe.compareAndSwapInt()方法,即Unsafe类的CAS操作。

使用示例如下图,用于标识程序执行过程中是否发生了异常,使用quartz实现高级定制化定时任务(包含管理界面)实现中:

参考资料:《深入理解Java虚拟机 第二版》

引申阅读: 使用quartz实现高级定制化定时任务(包含管理界面)

推荐阅读:elastic search搜索引擎实战demo:https://github.com/simonsfan/springboot-quartz-demo,分支:feature_es
---------------------
 
原文:https://blog.csdn.net/fanrenxiang/article/details/80623884

Java CAS同步机制 实践应用的更多相关文章

  1. Java CAS同步机制 原理详解(为什么并发环境下的COUNT自增操作不安全): Atomic原子类底层用的不是传统意义的锁机制,而是无锁化的CAS机制,通过CAS机制保证多线程修改一个数值的安全性。

    精彩理解:  https://www.jianshu.com/p/21be831e851e ;  https://blog.csdn.net/heyutao007/article/details/19 ...

  2. 浅谈Java多线程同步机制之同步块(方法)——synchronized

    在多线程访问的时候,同一时刻只能有一个线程能够用 synchronized 修饰的方法或者代码块,解决了资源共享.下面代码示意三个窗口购5张火车票: package com.jikexueyuan.t ...

  3. Java多线程同步机制之同步块(方法)——synchronized

    在多线程访问的时候,同一时刻只能有一个线程能够用 synchronized 修饰的方法或者代码块,解决了资源共享.下面代码示意三个窗口购5张火车票: package com.jikexueyuan.t ...

  4. 【总结】Java线程同步机制深刻阐述

    原文:http://hxraid.iteye.com/blog/667437 我们可以在计算机上运行各种计算机软件程序.每一个运行的程序可能包括多个独立运行的线程(Thread). 线程(Thread ...

  5. java多线程同步机制

    一.关键字: thread(线程).thread-safe(线程安全).intercurrent(并发的) synchronized(同步的).asynchronized(异步的). volatile ...

  6. Java 硬件同步机制 Swap 指令模拟 + 记录型信号量模拟

    学校实验存档//.. 以经典的生产者消费者问题作为背景. 进程同步方式接口: package method; /** * P表示通过,V表示释放 */ public interface Method ...

  7. Java硬件同步机制Swap指令模拟+记录型信号量模拟

    学校实验存档//.. 以经典的生产者消费者问题作为背景. 进程同步方式接口: package method; /** * P表示通过,V表示释放 */ public interface Method ...

  8. Java多线程同步机制(synchronized)

    参看:http://enetor.iteye.com/blog/986623

  9. Java多线程的同步机制(synchronized)

    一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在 java里边就是拿到某个同步对象的锁(一个对象只有一把锁): 如果这个时候同步对象的锁被其他线程拿走了,他(这个 ...

随机推荐

  1. R语言的精度和时间效率比较(简单版)

    R语言的最大数值 在R语言里面,所能计算的最大数值可以用下面的方法获得: ###R可计算最大数值 .Machine 在编程的时候注意不要超过这个数值.当然,普通情况下也不可能超过的. R语言的最大精度 ...

  2. 独家!DevExpress VCL Controls 2019发展路线图(No.1)

    [DevExpress VCL Controls下载] 根据调查结果和反馈,DevExpress官方团队最终确定了DevExpress VCL Controls 2019年的路线图. 关于调查结果的重 ...

  3. bootstrap中的.container类定义

    bootstrap中的.container类定义 .container{ padding-right:15px; padding-left:15px; margin-right:auto; margi ...

  4. centos下使用yum 安装pip

    本文为转载:原文出处:https://www.cnblogs.com/saolv/p/6963314.html centos下安装pip时失败: [root@wfm ~]# yum -y instal ...

  5. [ffmpeg]安装

    下载源码: 我们选择去官网http://ffmpeg.org下载.版本和打包方式譬如:ffmpeg-3.1.11.tar.bz2 安装过程: tar   -jxvf ./ffmpeg-3.1.11.t ...

  6. Delegate & Event

    Long time without coding,貌似对programming都失去了曾有的一点点sense了,今日有空再细瞄一下.net的委托和事件. Delegate 首先,委托用于引用一类具有相 ...

  7. Ruby中puts,print,p的区别

    如果字符串的行尾没有包含换行符,puts就会添加一个,但print不会: print会精确打印内容并让光标留在末尾(在某些系统平台,在程序输出的末尾会自动换行): p会输出一个审查字符串,它通常会包含 ...

  8. 基于springboot构建dubbo的入门demo

    之前记录了构建dubbo入门demo所需的环境以及基于普通maven项目构建dubbo的入门案例,今天记录在这些的基础上基于springboot来构建dubbo的入门demo:众所周知,springb ...

  9. mysql事件调用存储过程总结

    第一次写事件调用存储过程,在网上找了一些资料,特此做下总结,巩固一下: 事件调用存储过程主要有三种: (1)创建事件马上执行,调用存储过程 CREATE EVENT if not exists Eve ...

  10. 20165214 2018-2019-2 《网络对抗技术》Exp4 恶意代码分析 Week6

    <网络对抗技术>Exp3 免杀原理与实践 Week5 一.实验目标与内容 1.实践目标 1.1是监控你自己系统的运行状态,看有没有可疑的程序在运行. 1.2是分析一个恶意软件,就分析Exp ...