返回顶部
首页 > 资讯 > 后端开发 > Python >基于java中cas实现的探索
  • 185
分享到

基于java中cas实现的探索

2024-04-02 19:04:59 185人浏览 薄情痞子

Python 官方文档:入门教程 => 点击学习

摘要

目录1.背景简介2. java源码追踪3. hotspot JVM源码追踪4. 手写一个cas实现1. 通过汇编手写一个cas方法2. 多线程条件下测试自行实现的cas方法3. ca

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/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/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的研究吧

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

--结束END--

本文标题: 基于java中cas实现的探索

本文链接: https://lsjlt.com/news/160720.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 基于java中cas实现的探索
    目录1.背景简介2. java源码追踪3. hotspot jvm源码追踪4. 手写一个cas实现1. 通过汇编手写一个cas方法2. 多线程条件下测试自行实现的cas方法3. ca...
    99+
    2024-04-02
  • JDK基于CAS实现原子类盘点解析
    目录前言基础原子类原子引用原子数组原子字段更新器原子累加器总结前言 JDK中提供了一系列的基于CAS实现的原子类,CAS 的全称是Compare-And-Swap,底层是lock c...
    99+
    2022-12-08
    JDK CAS原子类 JDK CAS
  • oracle 实现基于函数的索引
    使用场景:当一个查询运行很慢。通过检查where子句,发现其中的一列应用了sql lower函数,lower函数阻止使用该列上现有的索引。你想要创建一个基于函数索引来支持这个查询,如下SQL>...
    99+
    2024-04-02
  • PHP 中基于 Elasticsearch 的模糊搜索与语义搜索实现
    在现代互联网环境下,搜索功能已经成为了各种应用的必备功能之一。传统的模糊搜索往往只能按照关键字进行简单的匹配,而缺乏了对用户意图的理解。而语义搜索则可以更好地抓住用户的意图,从而提供更加精确的搜索结果。在本文中,我们将介绍如何在 PHP 中...
    99+
    2023-10-21
    elasticsearch 模糊搜索 语义搜索
  • Java基于Netty实现Httpserver的实战
    目录HTTP协议基础知识Netty的http协议栈基于Netty实现http serverHTTP协议基础知识 HTTP(超文本传输协议,英文:HyperText Transfer...
    99+
    2024-04-02
  • Java语言中cas指令的无锁编程实现实例
    最开始接触到相关的内容应该是从volatile关键字开始的吧,知道它可以保证变量的可见性,而且利用它可以实现读与写的原子操作。。。但是要实现一些复合的操作volatile就无能为力了。。。最典型的代表是递增和递减的操作。。。。我们知道,在并...
    99+
    2023-05-31
    java cas 无锁算法
  • MySQL基于索引的压力测试的实现
    一、模拟数据库数据 1-1 创建数据库及表脚本 - vim slap.sh #!/bin/bash HOSTNAME="localhost" PORT=...
    99+
    2024-04-02
  • 基于Java实现Actor模型
    目录ActorNodeActorSystemActorSystem初始化创建Actor发送消息休眠Actor定时器小结Actor模型是一种常见的并发模型,与最常见的并发模型&mdas...
    99+
    2023-05-19
    Java实现Actor模型 Java Actor模型 Java Actor
  • Java基于HttpClient实现RPC的示例
    目录1 HttpClient简介2 代码实现2.1 服务端2.1.1 新建控制器2.1.2 新建启动器2.2 客户端2.2.1 添加依赖2.2.2 新建类3. Jackson用法3....
    99+
    2024-04-02
  • 详解elasticsearch实现基于拼音搜索
    目录1、背景2、安装拼音分词器3、拼音分词器提供的功能4、简单测试一下拼音分词器4.1 dsl4.2 运行结果5、es中分词器的组成6、自定义一个分词器实现拼音和中文的搜索1、创建m...
    99+
    2023-01-16
    elasticsearch 拼音搜索 elasticsearch 搜索
  • 如何基于solr实现全文检索
    这篇文章将为大家详细讲解有关如何基于solr实现全文检索,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。Solr是一个独立的企业级搜索应用服务器,它对外提供类似于Web-service的API接口。用户可以...
    99+
    2023-05-30
    solr
  • Java如何实现基于图的深度优先搜索和广度优先搜索
    这篇文章将为大家详细讲解有关Java如何实现基于图的深度优先搜索和广度优先搜索,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。1.新建一个表示“无向图”类NoDirectionGraphpackage&nb...
    99+
    2023-05-30
    java
  • 基于NodeJS的前后端分离的思考与实践(二)模版探索
    前言 在做前后端分离时,第一个关注到的问题就是 渲染,也就是 View 这个层面的工作。 在传统的开发模式中,浏览器端与服务器端是由不同的前后端两个团队开发,但是模版却又在这两者中间的模糊地带。因此模版上面...
    99+
    2022-06-04
    模版 后端 NodeJS
  • 基于Java实现双向链表
    本文实例为大家分享了Java实现双向链表的具体代码,供大家参考,具体内容如下 双向链表与单链表的对比: 1、单向链表查找只能是一个方向,双向链表可以向前或者向后查找2、单向链表不能自...
    99+
    2024-04-02
  • 基于Java实现Socket编程的方法
    这篇文章主要介绍“基于Java实现Socket编程的方法”,在日常操作中,相信很多人在基于Java实现Socket编程的方法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”基于Java实现Socket编程的方法...
    99+
    2023-06-29
  • Java如何实现基于数组的表
    这篇文章将为大家详细讲解有关Java如何实现基于数组的表,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。没看过 其他语言版的数据结构,但觉得java的实现方法很巧妙--用类和对象来实现.基于数组的表,思想很...
    99+
    2023-06-03
  • Android基于RecyclerView实现高亮搜索列表
    话不多说先看今天的实现的效果: 相信这种效果很多项目都会用到,今天就讲讲利用RecycleView来实现他,博主把此篇文章定位初级篇,可能因为这确实很简单,所以我要更要讲的详...
    99+
    2022-06-06
    列表 recyclerview 高亮 Android
  • 探索Laravel中的自然语言处理:Java和Bash的实现方式
    Laravel是一个流行的PHP框架,被广泛应用于web开发中。除了基本的web开发,Laravel还提供了许多强大的功能,其中之一是自然语言处理(NLP)。本文将介绍如何在Laravel中使用Java和Bash来实现NLP功能,并且穿插演...
    99+
    2023-08-28
    bash 自然语言处理 laravel
  • 智能安防监控:基于Java+SpringBoot实现人脸识别搜索
    目录 引言背景介绍目的和重要性 人脸识别技术的基本原理图像采集和预处理特征提取与表示人脸匹配算法 人脸识别搜索的应用领域公告安全和监控社交网络和照片管理 参考实现步骤数据收集与预处理人脸特征提取查询处理 引言 ...
    99+
    2023-08-16
    java spring boot 开发语言 AI 人脸搜索 人脸识别
  • Java 线程池:从概念到实现的深入探索
    线程池是一种管理线程集合的机制,它允许在应用程序中有效地利用线程资源。线程池减少了频繁创建和销毁线程的开销,从而提高了应用程序的性能和可扩展性。 主要功能 线程复用:线程池将线程预先创建并维护在一个池中,供任务使用,避免了重复创建线程的...
    99+
    2024-03-13
    线程池
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作