我的编程空间,编程开发者的网络收藏夹
学习永远不晚

基于java中cas实现的探索

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

基于java中cas实现的探索

1.背景简介

当我们在并发场景下,增加某个integer值的时,就涉及到多线程安全的问题,解决思路两个

  • 将值增加的方法使用同步代码块同步
  • 使用AtomicInteger,来逐步增加其值

这两种实现方式代码如下


import java.util.concurrent.atomic.AtomicInteger;
public class CASTest {
    private static AtomicInteger countAI = new AtomicInteger(0);
    private static int count = 0;
    private static final int THREAD_COUNT = 8;
    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread[] threads = new Thread[THREAD_COUNT];
        for(int i = 0; i < THREAD_COUNT; i++) {
            threads[i] = new Thread(){
                @Override
                public void run() {
                    for(int i = 0; i < 10000000; i++) {
//                        测试1:使用同步代码块方法,耗时:2927ms
                        synAdd();
//                        测试2:使用atomicInterger方式, 耗时:1860ms
//                        atomicAdd();
                    }
                }
            };
        }
        for(int i = 0; i < THREAD_COUNT; i++) {
            threads[i].start();
        }
        for(int i = 0; i < THREAD_COUNT; i++) {
            threads[i].join();
        }
        System.out.println("finish...耗时:" + (System.currentTimeMillis()-start) + "ms");
    }
    private static synchronized void synAdd() {
        count++;
    }
    private static void atomicAdd() {
        countAI.getAndAdd(1);
    }
}

从测试结果可以看出,使用atomicAdd方法耗时: 1860ms, 使用synAdd方法耗时: 2927ms

为何使用AtomicInteger效率更高?以及AtomicInteger是如何实现的?本文将对cas进行进一步探索

2. java源码追踪

根据断点追踪countAI.getAndAdd(1);, 对栈如下


getAndAddInt:1034, Unsafe (sun.misc)
getAndAdd:177, AtomicInteger (java.util.concurrent.atomic)
atomicAdd:45, CASTest (com.youai.cas)
access$000:5, CASTest (com.youai.cas)
run:21, CASTest$1 (com.youai.cas)

进入到了关键核心方法 sun.misc.Unsafe#getAndAddInt


    public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!compareAndSwapInt(o, offset, v, v + delta));
        return v;
    }

在这个方法中,循环调用了sun.misc.Unsafe#compareAndSwapInt这个方法,这个方法的效果就是,判断对象o中,地址偏移量是offset这个地址内存中的int值是否和期望值v相等,如果相等,则用v + delta替换,并返回替换成功;否则不替换,并返回替换失败。需要循环的原因是因为getIntVolatile(o, offset);和compareAndSwapInt(o, offset, v, v + delta)这两步并不是原子原作,在执行前面一句后,目标地址中的值可能被其他线程给修改,所以如果失败需要重新获取目标地址中的最新值。

可以看到,在整个代码过程中,并没有强制加锁,减少线程切换阻塞等无效时间的消耗,而是采用了失败重试的机制,这也是乐观锁的一种实现。因为它的效率高。

cas能够实现,需要compareAndSwapInt这个操作等价于一个原子操作,那compareAndSwapInt是如何实现的呢?下次解答。

3. hotspot jvm源码追踪



    public final native boolean compareAndSwapInt(Object o, long offset,
                                                  int expected,
                                                  int x);

可以看到compareAndSwapInt这个方法被native修饰,具体实现在需要参考c/c++代码:

从openjdk源码追踪到compareAndSwapInt的实现在hotspot/class="lazy" data-src/share/vm/prims/unsafe.cpp这文件中, 具体对应方法如下:


UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e; //此处调用了Atomic::cmpxchg方法
UNSAFE_END

Unsafe_CompareAndSwapInt方法进一步调用了Atomic::cmpxchg方法,由于Atomic::cmpxchg方法和平台有关,我们此时关注linux下的实现,hotspot/class="lazy" data-src/os_cpu/linux_x86/vm/atomic_linux_x86.inline.hpp,具体方法如下:


inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  int mp = os::is_MP();
  __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
                    : "=a" (exchange_value)
                    : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                    : "cc", "memory");
  return exchange_value;
}

此方法是一个c++内联汇编的方法,我们着重关注cmpxchgl这个汇编指令:

在这里插入图片描述

This instruction can be used with a LOCK prefix to allow the instruction to be executed atomically. To simplify the interface to the processor's bus, the destination operand receives a write cycle without regard to the result of the comparison. The destination operand is written back if the comparison fails; otherwise, the source operand is written into the destination. (The processor never produces a locked read without also producing a locked write.)

intel汇编指令的官方文档来看, cmpxchgl的作用是,比较ax寄存器中的值和期望值,如果相等,则将target值设置到目标对象上,否则不设置。特别得,在cmpxchgl指令前加上lock可以使得cmpxchgl操作成为一个原子操作。这也论证了sun.misc.Unsafe#compareAndSwapInt确是等价于一个原子操作

4. 手写一个cas实现

1. 通过汇编手写一个cas方法

看了intel的文档,cas原理并不复杂,可以通过汇编手写一个cas方法xchange:


	.file	"cmpandset.c"
	.text
	.globl	xchange
	.type	xchange, @function
xchange:
.LFB0:
	.cfi_startproc
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	.cfi_def_cfa_register 6
	mov %esi, %eax
	lock cmpxchgl %edx, (%rdi)
	sete %al
	movzbl %al, %eax
.L3:
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	xchange, .-xchange
	.ident	"GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
	.section	.note.GNU-stack,"",@progbits

2. 多线程条件下测试自行实现的cas方法

测试代码:


#include <stdio.h>
#include <pthread.h>
#include <sys/time.h>
#define THREAD_CNT 8
extern int xchange(int *ptr, int expect, int dest);
int a = 0;
void cmp_add(int* cnt, int adder);
long current_ms() {
    struct timeval cur_time;
    gettimeofday(&cur_time, NULL);
    return cur_time.tv_sec * 1000 + cur_time.tv_usec / 1000;
}
void * sum(void *arg) {
    for(int i = 0; i < 10000000; i++) {
        cmp_add(&a, 1);
    }
}
int main(int argc, char const *argv[])
{
    long start = current_ms();
    int result = xchange(&a, 13, 13);
    printf("result=%d\n", result);
    pthread_t tids[THREAD_CNT];
    for(int i = 0; i < THREAD_CNT; i++) {
        pthread_create(&tids[i], NULL, sum, NULL);
    }
    // 等待
    for(int i = 0; i < THREAD_CNT; i++) {
        pthread_join(tids[i], NULL);
    }
    printf("result=%d, 耗时:%ldms\n", a, (current_ms() - start));
    
    return 0;
}
void cmp_add(int* cnt, int adder) {
    int tmp = 0;
    do {
        tmp = *cnt;
    } while(xchange(cnt, tmp, tmp+adder) == 0);
}

输出结果为:

result=80000000, 耗时:8596ms

可见自行实现的cas方法在多线程场景下,同样是线程安全的。

3. cas与互斥锁方式的对比

测试代码:


#include <stdio.h>
#include <pthread.h>
#include <sys/time.h>
#include <semaphore.h>
#define THREAD_CNT 8
int a = 0;
sem_t add_mutex;
long current_ms() {
    struct timeval cur_time;
    gettimeofday(&cur_time, NULL);
    return cur_time.tv_sec * 1000 + cur_time.tv_usec / 1000;
}
void * sum(void *arg) {
    for(int i = 0; i < 10000000; i++) {
        sem_wait(&add_mutex);
        a++;
        sem_post(&add_mutex);
    }
}
int main(int argc, char const *argv[])
{
    long start = current_ms();
    
    sem_init(&add_mutex, 0, 1);
    pthread_t tids[THREAD_CNT];
    for(int i = 0; i < THREAD_CNT; i++) {
        pthread_create(&tids[i], NULL, sum, NULL);
    }
    
    // 等待
    for(int i = 0; i < THREAD_CNT; i++) {
        pthread_join(tids[i], NULL);
    }
    printf("result=%d, 耗时:%ldms\n", a, (current_ms() - start));
    sem_destroy(&add_mutex);
    
    return 0;
}

输出结果:

result=80000000, 耗时:19353ms

4. 结论

在c中,cas耗时8596ms, 互斥锁耗时19353ms, cas的执行效率显著高于互斥锁

5. 思考

各语言各版本,执行时间如下,单位ms:

实现方式 java c
cas 1860 8596
2927 19353

  • cas的方式效率比锁高
  • 开启了jit后的java代码为何效率比c更高?留待后续对jit的研究吧

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

基于java中cas实现的探索

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

PHP 中基于 Elasticsearch 的模糊搜索与语义搜索实现

在现代互联网环境下,搜索功能已经成为了各种应用的必备功能之一。传统的模糊搜索往往只能按照关键字进行简单的匹配,而缺乏了对用户意图的理解。而语义搜索则可以更好地抓住用户的意图,从而提供更加精确的搜索结果。在本文中,我们将介绍如何在 PHP 中
2023-10-21

Java语言中cas指令的无锁编程实现实例

最开始接触到相关的内容应该是从volatile关键字开始的吧,知道它可以保证变量的可见性,而且利用它可以实现读与写的原子操作。。。但是要实现一些复合的操作volatile就无能为力了。。。最典型的代表是递增和递减的操作。。。。我们知道,在并
2023-05-31

Java如何实现基于图的深度优先搜索和广度优先搜索

这篇文章将为大家详细讲解有关Java如何实现基于图的深度优先搜索和广度优先搜索,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。1.新建一个表示“无向图”类NoDirectionGraphpackage co
2023-05-30

基于NodeJS的前后端分离的思考与实践(二)模版探索

前言 在做前后端分离时,第一个关注到的问题就是 渲染,也就是 View 这个层面的工作。 在传统的开发模式中,浏览器端与服务器端是由不同的前后端两个团队开发,但是模版却又在这两者中间的模糊地带。因此模版上面总不可避免的越来越多复杂逻辑,最终
2022-06-04

基于Java实现Socket编程的方法

这篇文章主要介绍“基于Java实现Socket编程的方法”,在日常操作中,相信很多人在基于Java实现Socket编程的方法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”基于Java实现Socket编程的方法
2023-06-29

Java如何实现基于数组的表

这篇文章将为大家详细讲解有关Java如何实现基于数组的表,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。没看过 其他语言版的数据结构,但觉得java的实现方法很巧妙--用类和对象来实现.基于数组的表,思想很
2023-06-03

智能安防监控:基于Java+SpringBoot实现人脸识别搜索

目录 引言背景介绍目的和重要性 人脸识别技术的基本原理图像采集和预处理特征提取与表示人脸匹配算法 人脸识别搜索的应用领域公告安全和监控社交网络和照片管理 参考实现步骤数据收集与预处理人脸特征提取查询处理 引言
2023-08-16

探索Golang中除法运算符的实现原理

探索Golang中除法运算符的实现原理在Golang中,除法运算符/用于执行除法计算。但是,要了解除法运算符的实现原理,我们需要深入了解Golang中的底层实现机制。Golang是一门编译型语言,其底层代码由编译器生成。因此,我们无法直接
探索Golang中除法运算符的实现原理
2024-03-13

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录