多线程安全性:手写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

多线程安全性:手写CAS机制和Lock实现

CPU中的线程去获取i值进行操作时不满足原子性,两个线程获取的i值可能是相同的。因此程序执行的结果会少于20000;

解决办法:

1、加synchronized关键字

2、使用JUC中的atomic

3、使用Lock

二、CAS机制:compare and swap 比较和交换

属于硬件同步原语,处理器提供了基本内存操作的原子性保证。

cas操作需要输入两个数值,一个旧值A(期望操作前的值)和一个新值B,在操作期间会比较旧值有没有变化,如果没有变化,才换成新值,否则不进行交换。

多线程安全性:手写CAS机制和Lock实现

比较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()方法

多线程安全性:手写CAS机制和Lock实现

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();
		}
		
	}
}