多线程安全性:手写CAS机制和Lock实现
一、线程常见的 i++ 问题:使得i++ 循环执行20000次,最后打印出20000;
public class DemoCas {
int i = 0;
public void incr(){
i++;
}
public static void main(String[] args) {
DemoCas demo = new DemoCas();
for(int i =0;i<2;i++){
new Thread(()->{
for(int j=0;j<10000;j++){
demo.incr();
}
}).start();
}
try {
Thread.sleep(2000);
System.out.println("i="+demo.i);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
执行结果:i是不定值,一般情况下会少于20000.
多线程的原子性:原子操作可以是一个步骤,也可以是多个操作步骤,但是顺序不能被打乱,也不可以被切割而只执行其中的一部分。将整个操作视为一个整体是原子性的核心特征。
执行i++操作会被分为三个步骤执行:
1、获取 i 的值
2、进行i+1
3、把i+1的值赋给i
CPU中的线程去获取i值进行操作时不满足原子性,两个线程获取的i值可能是相同的。因此程序执行的结果会少于20000;
解决办法:
1、加synchronized关键字
2、使用JUC中的atomic
3、使用Lock
二、CAS机制:compare and swap 比较和交换
属于硬件同步原语,处理器提供了基本内存操作的原子性保证。
cas操作需要输入两个数值,一个旧值A(期望操作前的值)和一个新值B,在操作期间会比较旧值有没有变化,如果没有变化,才换成新值,否则不进行交换。
比较A与V,当A=V时,则把B赋值给V,A !=V 时,则此操作无效。这就是比较交换机制。CAS机制进行i++同样时三步操作:读取i值,i++,赋值。只是在第三步赋值前进行了一个比较的校验。
2.1 手写CAS实现
Unsafe 是jdk自带的操作内存的类。通过Unsafe 来获取 内存中的属性i,然后通过方法:
Unsafe.compareAndSwapInt(Object var1, long var2, int var4, int var5) 来进行比较交换实现cas机制。var1 表示当前操作的对象,var2 表示操作的属性在对象中的位置(偏移量),var4表示内存中的属性值(上图的A),var5表示修改后的属性值(上图的B)。
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class DemoCas {
int i = 0;
//Unsafe unsafe = Unsafe.getUnsafe()//不能直接使用,需要通过反射来获取
static Unsafe unsafe; // java 中操作内存的一个类
static long valueOffset; //内存中的地址(偏移量)
static {
try{//通过反射机制去拿unsafe
Field filed = Unsafe.class.getDeclaredField("theUnsafe"); //拿到theUnsafe
filed.setAccessible(true); //因为是私有,设置成可访问
//拿到unsafe 参数是要传一个对象,因为是静态的,没有对象所以传Null
unsafe = (Unsafe) filed.get(null);
//通过数据去拿到内存中的i地址(偏移量)
//直接操作内存,获取属性的偏移量(同它来定位对象内具体属性的内存地址)
valueOffset = unsafe.objectFieldOffset(DemoCas.class.getDeclaredField("i"));
}catch(Exception e){
e.printStackTrace();
}
}
public void incr(){
//比较和替换
int current =0;//内存中的值,当前值
do{
current = unsafe.getIntVolatile(this,valueOffset); //获取当前内存中的值
}while(!unsafe.compareAndSwapInt(this,valueOffset,current,current+1));
}
public static void main(String[] args) {
DemoCas demo = new DemoCas();
for(int i =0;i<2;i++){
new Thread(()->{
for(int j=0;j<10000;j++){
demo.incr();
}
}).start();
}
try {
Thread.sleep(2000);
System.out.println("i="+demo.i);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
2.2 CAS机制问题
1、循环+CAS的实现让所有的线程处于高频运行,争抢CPU的执行,长时间不成功,会带来很大的CPU资源消耗。
2、只能针对单个变量的操作,不能用于多个标量实现原子操作。
三、实现Lock的lock()和unlock()方法
boolean AtomicReference.compareAndSet(V expect, V update);是把新值update替换掉旧值expect,替换成功则返回ture.是线程安全的。
实现Lock 接口,重写lock()和unlock()方法
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
public class MyLock implements Lock{
//原子操作类
AtomicReference<Thread> owner = new AtomicReference<Thread>();
//拿不到锁的线程,需要一个等待的列表。
public LinkedBlockingDeque<Thread> waiter = new LinkedBlockingDeque<>();
@Override
public void lock() {
//进行当前线程设置
owner.compareAndSet(null,Thread.currentThread()); //成功则返回ture。循环进行处理
while(!owner.compareAndSet(null,Thread.currentThread())){//如果当前线程拿不到锁
//拿不到的情况,等待,加入等待列表中
waiter.add(Thread.currentThread());
LockSupport.park();//让当前线程等待(一直卡在这里)
//如果能够执行到这段的话,证明被唤醒了,所以要从等待列表中删除
waiter.remove(Thread.currentThread());
}
}
@Override
public void unlock() {
//当前线程与内存中的那个线程进行比较,如果是的,把内存中的线程至空
if(owner.compareAndSet(Thread.currentThread(),null)){
//锁被释放了,需要告知其他线程,所有线程可以去拿锁了(唤醒所有等待线程)
Object[] objects = waiter.toArray();//等待列表转成数组
for(Object object :objects){//遍历,拿到所有等待的线程
Thread next = (Thread)object;
LockSupport.unpark(next);// 把线程唤醒
}
}
}
@Override
public void lockInterruptibly() throws InterruptedException {
// TODO Auto-generated method stub
}
@Override
public boolean tryLock() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
// TODO Auto-generated method stub
return false;
}
@Override
public Condition newCondition() {
// TODO Auto-generated method stub
return null;
}
}
在程序中使用自己实现的myLock个程序上锁:
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class DemoCas2 {
MyLock myLock = new MyLock();
int i = 0;
public void incr(){
//比较和替换
myLock.lock();
i++;
myLock.unlock();
}
public static void main(String[] args) {
DemoCas2 demo = new DemoCas2();
for(int i =0;i<2;i++){
new Thread(()->{
for(int j=0;j<10000;j++){
demo.incr();
}
}).start();
}
try {
Thread.sleep(2000);
System.out.println("i="+demo.i);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}