无锁:CAS实现计数器
无锁(Lock-free):锁无关的程序,一个锁无关的程序能够确保它所有线程中至少有一个能够继续往下执行,而有些线程可能会被的延迟。然而在整体上,在某个时刻至少有一个线程能够执行下去。作为整体进程总是在前进的,尽管有些线程的进度可能没有其它线程进行的快。
技术实现:原子操作(CAS)
实现一个计数器的例子
public class Counter implements Serializable { //底层操作内存的对象 private static Unsafe unsafe; //偏移量 private static long valueOffset; //执行次数 private volatile int i =0; static { try { //因为unsafe对象只能通过反射获取 theUnsafe是底层unsafe的名称 Field filed = Unsafe.class.getDeclaredField("theUnsafe"); //因为该属性是私有的,所有手动修改为可见 filed.setAccessible(true); //通过字段获取对象 unsafe = (Unsafe) filed.get(null); } catch (Exception e) { e.printStackTrace(); } try { //获取object对象里i属性的偏移量 valueOffset = unsafe.objectFieldOffset(Counter.class.getDeclaredField("i")); } catch (NoSuchFieldException e) { e.printStackTrace(); } } public int getI(){ return i; } public void addI(){ for (;;) { int current = i; //获取当前次数值赋给自己线程 旧值 int result = current + 1; //将自己线程的值+1 新值 try { //通过unsale对象判断 旧值是否改变,如果改变了就返回false,继续循环 没有改变就自己将旧值改为新值 if(unsafe.compareAndSwapInt(this, valueOffset, current, result)){ break; } } catch (Exception e) { e.printStackTrace(); } } }
public class CasMain { public static void main(String[] args) throws InterruptedException { final Counter counter = new Counter(); long start = System.currentTimeMillis(); //开启2w个线程模拟高并发 for (int i=0;i<2;i++){ for (int j=0;j<10000;j++){ new Thread(new Runnable() { @Override public void run() { counter.addI(); } }).start(); } } long end = System.currentTimeMillis(); System.out.println("花费时间:"+(end-start)); Thread.sleep(2000); //最后打印结果 System.out.println(counter.getI()); } }
优点:不会发生死锁,不会有优先级倒置,进行CAS操作的消耗比加锁操作轻很多等等
缺点:基于循环CAS实现的各种自旋锁不适合做操作和等待时间太长的并发操作,循环CAS操作对时会大量占用cpu,对系统时间的开销也是很大,在开发维护的成本和复杂度上,无锁编程难度非常高,存在ABA问题。
ABA问题:线程a先读取了要对比的值i后,被线程b抢占了,线程b对i进行了修改后又改回i原来的值,线程1继续运行执行CAS操作的时候,无法判断出i的值被改过又改回来,因为它的CAS操作判断的是指针的地址,这个地址被重用的可能性很大(地址被重用是很经常发生的,一个内存分配后释放了,再分配,很有可能还是原来的地址,内存管理中重用内存基本上是一种很常见的行为)