背景
实际项目中经常会有用户设置唯一用户名的需求,比如我们设置了用户名alan,提交时
如果已经被注册,则给一个提示,并推荐一个数字后缀,比如alan0,alan1
如果我们在设置数字的同时,有并发的用户也和我们起了同样的名字比如同时都是alan0
那么就给后提交的用户返回alan1,如果都起名alan1,那么后提交的自动变为alan2
虽然这种情况极少,而且也可以在单服部署的时候使用synchronized,lock等同步器解决
最后也可以通过数据库层面去判断,但是这种极少的情况其实也有另一种叫解决方案 就是基于java UNSafe
compareAndSwapXXX 实现自定义的atomic cas乐观锁解决方案
如何实现自定义的atomic cas原子类
什么是cas
cas 全称就是compareAndSwap(比较和交换),描述为操作通过将内存中的值与指定数据进行比较,当数值一样时将内存中的数据替换为新的值
举例说明,比如原始值是1,那么你想把它加1后更新为加1之后的2,单线程下是没问题的 ,这是理想情况,但是多线程的情况下 你取出来的时候是1,然后你做了+1 变成了2,这时候你要提交变更,但是其实你提交之前已经有人提交了3
这时候你再提交2的话就会把别人的操作覆盖了,你这时候就得重新取出来3,然后+1变为4,然后再提交4,以此类推
所以判断的条件就是你提交时要变更的那个值必须等于你变更前出去来的值,这时候再放入新值才是正确的 这就是乐观锁的实现原理
UNSafe compareAndSwapXXX cas 简介
UNSage 类是sun.misc 包下的操作类,主要是对内存,volidate,cas,线程调度,类加载等的直接操作类
主要操作有如下图:
本文主要是基于cas 功能介绍,核心方法如下:
//针对对象的cas操作
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
//针对int类型的cas操作
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
//针对long类型的cas操作
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
java.util.concurrent.atomic.* 实现源码(内部调用的UNSafe compareAndSwapXXX )解析
拿 getAndIncrement 源码举例说明 AtomicInteger 实现原理
首先是初始化Unsafe 并获取value 的valueOffset 地址,也就value这个值本身的栈里指向的堆地址
然后调用 unsafe.getAndAddInt 进行cas 之后将原值+1,源码如下
实现基于UNSafe compareAndSwapXXX 的自定义atomic 重命名类核心代码以及说明
首先我们创建一个原子用户类AtomicUser 负责用户重命名id,
初始化Unsafe类,(由于我们不是system classLoader加载的类,所以只能反射获取,不然会报SecurityException ),初始化源码如下:
然后获取user 用户的内存地址,也就是我们重命名的用户的dto类,源码如下图:
重写equals 和hashcode 确定对比id和name结合算名字重复,源码如下图:
然后是我们cas 的核心代码,循环+1判断是否是当前的最新值,如果是则返回最新命名,cas更新最新值为当前值,也就是用户名,源码如下图:
然后是测试类,启动两个线程模拟提交不同编号名字:
完整源码如下
AtomicUser.java
package com.foundation.one.jdk8.sun.misc;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.Objects;
/**
* @Author:alan.wang
* @name:AtomicUser
* @description: AtomicUser 自动生成最新用户名
*/
public class AtomicUser {
private static long userOffet;
private volatile User currentUser ;
private static Unsafe U;
public AtomicUser(int id, String name) {
this.currentUser = new User(id,name);
}
class User {
private Integer id;
private String name ;
public User(Integer id,String name){
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public User idIncrement(){
this.id++;
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(id, user.id) && Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
static {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Object o = field.get(null);
U = (Unsafe) o;
userOffet = U.objectFieldOffset(AtomicUser.class.getDeclaredField("currentUser"));
}catch (Exception e){
e.printStackTrace();
}
}
public User canUseUsername(){
User user ;
do{
user = (User) U.getObjectVolatile(this,userOffet);
System.out.println("线程"+Thread.currentThread().getId()+",名称 :"+user.getName()+user.getId()+"重复");
} while (!U.compareAndSwapObject(this,userOffet,user,user.idIncrement()));
System.out.println("线程"+Thread.currentThread().getId()+",名称 :"+user.getName()+user.getId()+"不重复,确定使用,然后更新");
return user;
}
}
@Test
public void testCasUsername() throws ExecutionException, InterruptedException {
AtomicUser atomicUser = new AtomicUser(0, "jack");
Executor executor = new ThreadPoolExecutor(2, 2,
10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1));
CompletableFuture completableFuture1 = CompletableFuture.runAsync(() -> {
int i = 1;
while(i++<100) {
atomicUser.canUseUsername();
}
}, executor);
CompletableFuture completableFuture2 = CompletableFuture.runAsync(() -> {
int i = 1;
while(i++<100) {
atomicUser.canUseUsername();
}
}, executor);
completableFuture1.acceptEither(completableFuture2,(r)->{
}).get();
}
执行即如果如下:
线程12,名称 :jack0重复
线程12,名称 :jack1不重复,确定使用,然后更新
线程12,名称 :jack1重复
线程12,名称 :jack2不重复,确定使用,然后更新
线程12,名称 :jack2重复
线程12,名称 :jack3不重复,确定使用,然后更新
线程12,名称 :jack3重复
线程12,名称 :jack4不重复,确定使用,然后更新
线程12,名称 :jack4重复
线程12,名称 :jack5不重复,确定使用,然后更新
线程12,名称 :jack5重复
线程13,名称 :jack4重复
线程12,名称 :jack6不重复,确定使用,然后更新
线程13,名称 :jack7不重复,确定使用,然后更新
线程12,名称 :jack7重复
线程13,名称 :jack7重复
线程12,名称 :jack8不重复,确定使用,然后更新
线程13,名称 :jack9不重复,确定使用,然后更新
线程12,名称 :jack9重复
线程13,名称 :jack9重复
线程12,名称 :jack10不重复,确定使用,然后更新
线程13,名称 :jack11不重复,确定使用,然后更新
线程12,名称 :jack11重复
线程13,名称 :jack11重复
线程12,名称 :jack12不重复,确定使用,然后更新
线程13,名称 :jack13不重复,确定使用,然后更新
线程12,名称 :jack13重复
线程13,名称 :jack13重复
线程12,名称 :jack14不重复,确定使用,然后更新
线程13,名称 :jack15不重复,确定使用,然后更新
线程12,名称 :jack15重复
线程13,名称 :jack15重复
线程12,名称 :jack16不重复,确定使用,然后更新
线程13,名称 :jack17不重复,确定使用,然后更新
线程12,名称 :jack17重复
线程13,名称 :jack17重复
线程12,名称 :jack18不重复,确定使用,然后更新
线程13,名称 :jack19不重复,确定使用,然后更新
线程12,名称 :jack19重复
线程13,名称 :jack19重复
线程12,名称 :jack20不重复,确定使用,然后更新
线程13,名称 :jack21不重复,确定使用,然后更新
线程12,名称 :jack21重复
线程13,名称 :jack21重复
线程12,名称 :jack22不重复,确定使用,然后更新
线程13,名称 :jack23不重复,确定使用,然后更新
线程12,名称 :jack23重复
线程13,名称 :jack23重复
线程12,名称 :jack24不重复,确定使用,然后更新
线程13,名称 :jack25不重复,确定使用,然后更新
线程13,名称 :jack25重复
线程12,名称 :jack25重复
线程13,名称 :jack26不重复,确定使用,然后更新
线程12,名称 :jack27不重复,确定使用,然后更新
线程13,名称 :jack27重复
线程12,名称 :jack27重复
线程13,名称 :jack28不重复,确定使用,然后更新
线程12,名称 :jack29不重复,确定使用,然后更新
线程13,名称 :jack29重复
线程12,名称 :jack29重复
线程13,名称 :jack30不重复,确定使用,然后更新
线程13,名称 :jack31重复
线程13,名称 :jack32不重复,确定使用,然后更新
线程12,名称 :jack32不重复,确定使用,然后更新
线程13,名称 :jack32重复
线程12,名称 :jack32重复
线程13,名称 :jack33不重复,确定使用,然后更新
线程12,名称 :jack34不重复,确定使用,然后更新
线程12,名称 :jack34重复
线程12,名称 :jack35不重复,确定使用,然后更新
线程13,名称 :jack34重复
线程12,名称 :jack35重复
线程13,名称 :jack36不重复,确定使用,然后更新
线程12,名称 :jack37不重复,确定使用,然后更新
线程13,名称 :jack37重复
线程12,名称 :jack37重复
线程13,名称 :jack38不重复,确定使用,然后更新
线程12,名称 :jack39不重复,确定使用,然后更新
线程13,名称 :jack39重复
线程12,名称 :jack39重复
线程13,名称 :jack40不重复,确定使用,然后更新
线程12,名称 :jack41不重复,确定使用,然后更新
线程13,名称 :jack41重复
线程12,名称 :jack41重复
线程13,名称 :jack42不重复,确定使用,然后更新
线程12,名称 :jack43不重复,确定使用,然后更新
线程13,名称 :jack43重复
线程12,名称 :jack43重复
线程13,名称 :jack44不重复,确定使用,然后更新
线程12,名称 :jack45不重复,确定使用,然后更新
线程13,名称 :jack45重复
线程12,名称 :jack45重复
线程13,名称 :jack46不重复,确定使用,然后更新
线程12,名称 :jack47不重复,确定使用,然后更新
线程13,名称 :jack47重复
线程12,名称 :jack47重复
线程13,名称 :jack48不重复,确定使用,然后更新
线程12,名称 :jack49不重复,确定使用,然后更新
线程13,名称 :jack49重复
线程12,名称 :jack49重复
线程13,名称 :jack50不重复,确定使用,然后更新
线程12,名称 :jack51不重复,确定使用,然后更新
线程13,名称 :jack51重复
线程12,名称 :jack51重复
线程13,名称 :jack52不重复,确定使用,然后更新
线程13,名称 :jack53重复
线程13,名称 :jack54不重复,确定使用,然后更新
线程13,名称 :jack54重复
线程13,名称 :jack55不重复,确定使用,然后更新
线程13,名称 :jack55重复
线程12,名称 :jack55重复
线程13,名称 :jack56不重复,确定使用,然后更新
线程12,名称 :jack56重复
线程13,名称 :jack56重复
线程12,名称 :jack57不重复,确定使用,然后更新
线程13,名称 :jack58不重复,确定使用,然后更新
线程12,名称 :jack58重复
线程12,名称 :jack59不重复,确定使用,然后更新
线程12,名称 :jack59重复
线程13,名称 :jack58重复
线程12,名称 :jack60不重复,确定使用,然后更新
线程13,名称 :jack61不重复,确定使用,然后更新
线程13,名称 :jack61重复
线程12,名称 :jack61重复
线程13,名称 :jack62不重复,确定使用,然后更新
线程13,名称 :jack63重复
线程12,名称 :jack63不重复,确定使用,然后更新
线程13,名称 :jack64不重复,确定使用,然后更新
线程12,名称 :jack64重复
线程13,名称 :jack64重复
线程13,名称 :jack66不重复,确定使用,然后更新
线程12,名称 :jack65不重复,确定使用,然后更新
线程12,名称 :jack66重复
线程12,名称 :jack67不重复,确定使用,然后更新
线程13,名称 :jack66重复
线程12,名称 :jack67重复
线程13,名称 :jack68不重复,确定使用,然后更新
线程12,名称 :jack69不重复,确定使用,然后更新
线程13,名称 :jack69重复
线程13,名称 :jack70不重复,确定使用,然后更新
线程12,名称 :jack69重复
线程13,名称 :jack70重复
线程12,名称 :jack71不重复,确定使用,然后更新
线程13,名称 :jack72不重复,确定使用,然后更新
线程12,名称 :jack72重复
线程13,名称 :jack72重复
线程12,名称 :jack73不重复,确定使用,然后更新
线程13,名称 :jack74不重复,确定使用,然后更新
线程12,名称 :jack74重复
线程13,名称 :jack74重复
线程12,名称 :jack75不重复,确定使用,然后更新
线程13,名称 :jack76不重复,确定使用,然后更新
线程12,名称 :jack76重复
线程13,名称 :jack76重复
线程12,名称 :jack77不重复,确定使用,然后更新
线程13,名称 :jack78不重复,确定使用,然后更新
线程12,名称 :jack78重复
线程13,名称 :jack78重复
线程12,名称 :jack79不重复,确定使用,然后更新
线程13,名称 :jack80不重复,确定使用,然后更新
线程12,名称 :jack80重复
线程13,名称 :jack80重复
线程12,名称 :jack81不重复,确定使用,然后更新
线程13,名称 :jack82不重复,确定使用,然后更新
线程12,名称 :jack82重复
线程13,名称 :jack82重复
线程12,名称 :jack83不重复,确定使用,然后更新
线程13,名称 :jack84不重复,确定使用,然后更新
线程12,名称 :jack84重复
线程12,名称 :jack85不重复,确定使用,然后更新
线程12,名称 :jack85重复
线程12,名称 :jack86不重复,确定使用,然后更新
线程12,名称 :jack86重复
线程12,名称 :jack87不重复,确定使用,然后更新
线程12,名称 :jack87重复
线程12,名称 :jack88不重复,确定使用,然后更新
线程12,名称 :jack88重复
线程12,名称 :jack89不重复,确定使用,然后更新
线程12,名称 :jack89重复
线程12,名称 :jack90不重复,确定使用,然后更新
线程12,名称 :jack90重复
线程12,名称 :jack91不重复,确定使用,然后更新
线程12,名称 :jack91重复
线程12,名称 :jack92不重复,确定使用,然后更新
线程12,名称 :jack92重复
线程13,名称 :jack84重复
线程12,名称 :jack93不重复,确定使用,然后更新
线程13,名称 :jack94不重复,确定使用,然后更新
线程12,名称 :jack94重复
线程13,名称 :jack94重复
线程12,名称 :jack95不重复,确定使用,然后更新
线程13,名称 :jack96不重复,确定使用,然后更新
线程12,名称 :jack96重复
线程13,名称 :jack96重复
线程12,名称 :jack97不重复,确定使用,然后更新
线程13,名称 :jack98不重复,确定使用,然后更新
线程12,名称 :jack98重复
线程13,名称 :jack98重复
线程12,名称 :jack99不重复,确定使用,然后更新
线程13,名称 :jack100不重复,确定使用,然后更新
线程13,名称 :jack100重复
线程12,名称 :jack100重复
线程13,名称 :jack101不重复,确定使用,然后更新
线程12,名称 :jack102不重复,确定使用,然后更新
线程13,名称 :jack102重复
线程12,名称 :jack102重复
线程13,名称 :jack103不重复,确定使用,然后更新
线程12,名称 :jack104不重复,确定使用,然后更新
线程13,名称 :jack104重复
线程12,名称 :jack104重复
线程13,名称 :jack105不重复,确定使用,然后更新
线程12,名称 :jack106不重复,确定使用,然后更新
线程13,名称 :jack106重复
线程12,名称 :jack106重复
线程13,名称 :jack107不重复,确定使用,然后更新
线程12,名称 :jack108不重复,确定使用,然后更新
线程13,名称 :jack108重复
线程12,名称 :jack108重复
线程13,名称 :jack109不重复,确定使用,然后更新
线程12,名称 :jack110不重复,确定使用,然后更新
线程13,名称 :jack110重复
线程12,名称 :jack110重复
线程13,名称 :jack111不重复,确定使用,然后更新
线程12,名称 :jack112不重复,确定使用,然后更新
线程13,名称 :jack112重复
线程12,名称 :jack112重复
线程13,名称 :jack113不重复,确定使用,然后更新
线程12,名称 :jack114不重复,确定使用,然后更新
线程13,名称 :jack114重复
线程12,名称 :jack114重复
线程13,名称 :jack115不重复,确定使用,然后更新
线程12,名称 :jack116不重复,确定使用,然后更新
线程13,名称 :jack116重复
线程12,名称 :jack116重复
线程13,名称 :jack117不重复,确定使用,然后更新
线程12,名称 :jack118不重复,确定使用,然后更新
线程13,名称 :jack118重复
线程12,名称 :jack118重复
线程13,名称 :jack119不重复,确定使用,然后更新
线程12,名称 :jack120不重复,确定使用,然后更新
线程13,名称 :jack120重复
线程12,名称 :jack120重复
线程13,名称 :jack121不重复,确定使用,然后更新
线程12,名称 :jack122不重复,确定使用,然后更新
线程13,名称 :jack122重复
线程12,名称 :jack122重复
线程13,名称 :jack123不重复,确定使用,然后更新
线程12,名称 :jack124不重复,确定使用,然后更新
线程13,名称 :jack124重复
线程12,名称 :jack124重复
线程13,名称 :jack125不重复,确定使用,然后更新
线程12,名称 :jack126不重复,确定使用,然后更新
线程13,名称 :jack126重复
线程12,名称 :jack126重复
线程13,名称 :jack127不重复,确定使用,然后更新
线程12,名称 :jack128不重复,确定使用,然后更新
线程13,名称 :jack128重复
线程12,名称 :jack128重复
线程13,名称 :jack129不重复,确定使用,然后更新
线程12,名称 :jack130不重复,确定使用,然后更新
线程13,名称 :jack130重复
线程12,名称 :jack130重复
线程13,名称 :jack131不重复,确定使用,然后更新
线程12,名称 :jack132不重复,确定使用,然后更新
线程13,名称 :jack132重复
线程12,名称 :jack132重复
线程13,名称 :jack133不重复,确定使用,然后更新
线程12,名称 :jack134不重复,确定使用,然后更新
线程13,名称 :jack134重复
线程12,名称 :jack134重复
线程13,名称 :jack135不重复,确定使用,然后更新
线程12,名称 :jack136不重复,确定使用,然后更新
线程13,名称 :jack136重复
线程12,名称 :jack136重复
线程13,名称 :jack137不重复,确定使用,然后更新
线程12,名称 :jack138不重复,确定使用,然后更新
线程13,名称 :jack138重复
线程12,名称 :jack138重复
线程13,名称 :jack139不重复,确定使用,然后更新
线程12,名称 :jack140不重复,确定使用,然后更新
线程13,名称 :jack140重复
线程12,名称 :jack140重复
线程13,名称 :jack141不重复,确定使用,然后更新
线程12,名称 :jack142不重复,确定使用,然后更新
线程13,名称 :jack142重复
线程12,名称 :jack142重复
线程13,名称 :jack143不重复,确定使用,然后更新
线程12,名称 :jack144不重复,确定使用,然后更新
线程13,名称 :jack144重复
线程12,名称 :jack144重复
线程13,名称 :jack145不重复,确定使用,然后更新
线程12,名称 :jack146不重复,确定使用,然后更新
线程13,名称 :jack146重复
线程12,名称 :jack146重复
线程13,名称 :jack147不重复,确定使用,然后更新
线程12,名称 :jack148不重复,确定使用,然后更新
线程13,名称 :jack148重复
线程12,名称 :jack148重复
线程13,名称 :jack149不重复,确定使用,然后更新
线程12,名称 :jack150不重复,确定使用,然后更新
线程13,名称 :jack150重复
线程12,名称 :jack150重复
线程13,名称 :jack151不重复,确定使用,然后更新
线程12,名称 :jack152不重复,确定使用,然后更新
线程13,名称 :jack152重复
线程12,名称 :jack152重复
线程13,名称 :jack153不重复,确定使用,然后更新
线程12,名称 :jack154不重复,确定使用,然后更新
线程13,名称 :jack154重复
线程12,名称 :jack154重复
线程13,名称 :jack155不重复,确定使用,然后更新
线程12,名称 :jack156不重复,确定使用,然后更新
线程13,名称 :jack156重复
线程12,名称 :jack156重复
线程13,名称 :jack157不重复,确定使用,然后更新
线程12,名称 :jack158不重复,确定使用,然后更新
线程13,名称 :jack158重复
线程12,名称 :jack158重复
线程12,名称 :jack160不重复,确定使用,然后更新
线程12,名称 :jack160重复
线程13,名称 :jack159不重复,确定使用,然后更新
线程12,名称 :jack161不重复,确定使用,然后更新
线程13,名称 :jack161重复
线程12,名称 :jack161重复
线程13,名称 :jack162不重复,确定使用,然后更新
线程12,名称 :jack163不重复,确定使用,然后更新
线程13,名称 :jack163重复
线程12,名称 :jack163重复
线程13,名称 :jack164不重复,确定使用,然后更新
线程12,名称 :jack165不重复,确定使用,然后更新
线程12,名称 :jack165重复
线程13,名称 :jack165重复
线程12,名称 :jack166不重复,确定使用,然后更新
线程13,名称 :jack167不重复,确定使用,然后更新
线程12,名称 :jack167重复
线程13,名称 :jack167重复
线程12,名称 :jack168不重复,确定使用,然后更新
线程13,名称 :jack169不重复,确定使用,然后更新
线程12,名称 :jack169重复
线程13,名称 :jack169重复
线程12,名称 :jack170不重复,确定使用,然后更新
线程12,名称 :jack171重复
线程12,名称 :jack172不重复,确定使用,然后更新
线程12,名称 :jack172重复
线程13,名称 :jack171不重复,确定使用,然后更新
线程12,名称 :jack173不重复,确定使用,然后更新
线程12,名称 :jack173重复
线程12,名称 :jack174不重复,确定使用,然后更新
线程12,名称 :jack174重复
线程12,名称 :jack175不重复,确定使用,然后更新
线程12,名称 :jack175重复
线程12,名称 :jack176不重复,确定使用,然后更新
线程12,名称 :jack176重复
线程13,名称 :jack173重复
线程12,名称 :jack177不重复,确定使用,然后更新
线程12,名称 :jack178重复
线程13,名称 :jack178不重复,确定使用,然后更新
线程12,名称 :jack179不重复,确定使用,然后更新
线程12,名称 :jack179重复
线程12,名称 :jack180不重复,确定使用,然后更新
线程13,名称 :jack179重复
线程13,名称 :jack181不重复,确定使用,然后更新
线程13,名称 :jack181重复
线程13,名称 :jack182不重复,确定使用,然后更新
线程13,名称 :jack182重复
线程13,名称 :jack183不重复,确定使用,然后更新
线程13,名称 :jack183重复
线程13,名称 :jack184不重复,确定使用,然后更新
线程13,名称 :jack184重复
线程13,名称 :jack185不重复,确定使用,然后更新
线程13,名称 :jack185重复
线程13,名称 :jack186不重复,确定使用,然后更新
线程13,名称 :jack186重复
线程13,名称 :jack187不重复,确定使用,然后更新
线程13,名称 :jack187重复
线程13,名称 :jack188不重复,确定使用,然后更新
线程13,名称 :jack188重复
线程13,名称 :jack189不重复,确定使用,然后更新
线程13,名称 :jack189重复
线程13,名称 :jack190不重复,确定使用,然后更新
线程13,名称 :jack190重复
线程13,名称 :jack191不重复,确定使用,然后更新
线程13,名称 :jack191重复
线程13,名称 :jack192不重复,确定使用,然后更新
线程13,名称 :jack192重复
线程13,名称 :jack193不重复,确定使用,然后更新
线程13,名称 :jack193重复
线程13,名称 :jack194不重复,确定使用,然后更新
线程13,名称 :jack194重复
线程13,名称 :jack195不重复,确定使用,然后更新
线程13,名称 :jack195重复
线程13,名称 :jack196不重复,确定使用,然后更新
线程13,名称 :jack196重复
线程13,名称 :jack197不重复,确定使用,然后更新
线程13,名称 :jack197重复
线程13,名称 :jack198不重复,确定使用,然后更新