CAS和AQS
Posted wk-missq1
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CAS和AQS相关的知识,希望对你有一定的参考价值。
CAS的概念:
CAS的全称为Compare And Swap,直译就是比较交换。是一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值,其实现方式是基于硬件平台的汇编指令,在intel的CPU中,使用的是cmpxchg指令,就是说CAS是靠硬件实现的,从而在硬件层面提升效率。
CAS的原理:
CAS (compareAndSwap),中文叫比较交换,一种无锁原子算法。过程是这样:它包含 3 个参数 CAS(V,E,N),V表示要更新变量的值,E表示预期值,N表示新值。仅当 V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做两个更新,则当前线程则什么都不做。最后,CAS 返回当前V的真实值。CAS 操作时抱着乐观的态度进行的,它总是认为自己可以成功完成操作。
AQS的概念:
AQS(abstractQueueSychronizer)同步发生器。通过内置得到FIFO同步队列来完成线程争夺资源的管理工作。
AtomicInteger的用法:
1 /** 2 * AtomicInteger的用法 3 * @author Administrator 4 * 5 */ 6 public class CAS2 { 7 //声明原子型变量 8 private static AtomicInteger atomic1 = new AtomicInteger(100); 9 10 public static void main(String[] args) throws Exception { 11 /** 12 * compareAndSet(期望值,改变值) 13 */ 14 Thread t1 = new Thread(()->{ 15 System.out.println("t1:"+atomic1.compareAndSet(100, 110)); 16 System.out.println("t1.atomic1:"+atomic1.get()); 17 }); 18 t1.start(); 19 20 Thread t2 = new Thread(()->{ 21 try { 22 TimeUnit.SECONDS.sleep(2); 23 } catch (InterruptedException e) { 24 e.printStackTrace(); 25 } 26 System.out.println("t2:"+atomic1.compareAndSet(110, 100)); 27 System.out.println("t2.atomic1:"+atomic1.get()); 28 }); 29 t2.start(); 30 31 Thread t3 = new Thread(()->{ 32 try { 33 TimeUnit.SECONDS.sleep(3); 34 } catch (InterruptedException e) { 35 e.printStackTrace(); 36 } 37 System.out.println("t3:"+atomic1.compareAndSet(100, 120)); 38 System.out.println("t3.atomic1:"+atomic1.get()); 39 }); 40 t3.start(); 41 } 42 }
AtomicStampedReference的用法:
1 /** 2 * AtomicStampedReference的用法 3 * @author Administrator 4 * 5 */ 6 public class CAS3 { 7 8 private static AtomicStampedReference<Integer> asf = new AtomicStampedReference<Integer>(100, 1); 9 10 public static void main(String[] args) throws Exception { 11 12 /** 13 * compareAndSet(期望值,改变值,当前版本号,改变后的版本号) 14 */ 15 Thread t1 = new Thread(()->{ 16 System.out.println("t1:"+asf.compareAndSet(100, 110, asf.getStamp(), asf.getStamp()+1)); 17 System.out.println("t1.asf:"+asf.getReference()); 18 }); 19 t1.start(); 20 21 Thread t2 = new Thread(()->{ 22 try { 23 TimeUnit.SECONDS.sleep(2); 24 } catch (InterruptedException e) { 25 e.printStackTrace(); 26 } 27 System.out.println("t2:"+asf.compareAndSet(110,100,asf.getStamp(),asf.getStamp()+1)); 28 System.out.println("t2.asf:"+asf.getReference()); 29 }); 30 t2.start(); 31 32 Thread t3 = new Thread(()->{ 33 try { 34 TimeUnit.SECONDS.sleep(3); 35 } catch (InterruptedException e) { 36 e.printStackTrace(); 37 } 38 System.out.println("t3:"+asf.compareAndSet(100, 120,asf.getStamp(),asf.getStamp()+1)); 39 System.out.println("t3.asf:"+asf.getReference()); 40 }); 41 t3.start(); 42 } 43 44 }
通过实现Java的Lock接口来模拟一个锁的实现功能:
1 /** 2 * 模拟实现锁的方法 3 * @author Administrator 4 * 5 */ 6 public class MyLock implements Lock { 7 8 //实例化帮助器 9 private Helper helper = new Helper(); 10 /** 11 * 定义内部类帮助器,方便下面方法的执行 12 * @author Administrator 13 * 14 */ 15 private class Helper extends AbstractQueuedSynchronizer{ 16 //以独占的方式获取锁 17 @Override 18 protected boolean tryAcquire(int arg) { 19 //调用父类的获取状态的方法 20 int state = getState(); 21 if(state == 0){//如果状态为0 ,那说明可以获得锁 22 //利用CAS原理修改state,保证原子性 23 boolean stateFlag = compareAndSetState(0, arg); 24 if(stateFlag){//如果未true,那么说明当前可以获得锁,进行状态修改 25 //设置当前线程占有资源 26 setExclusiveOwnerThread(Thread.currentThread()); 27 return true;//返回值,说明获取锁成功 28 } 29 }else if(getExclusiveOwnerThread() == Thread.currentThread()){//判断当前占有的线程是不是请求的线程 30 //增加可重入性功能 31 setState(getState()+arg); 32 return true; 33 } 34 //其他情况设置为false,说明获取锁失败 35 return false; 36 } 37 38 //释放锁 39 @Override 40 protected boolean tryRelease(int arg) { 41 //获取当前锁的状态 42 int state = getState()-arg; 43 //设置标志位 44 boolean flag = false; 45 //判断释放当前锁后,状态是否为0 46 if(state == 0){//说明当前线程锁全部释放 47 setExclusiveOwnerThread(null); 48 flag=true; 49 } 50 setState(state); 51 return flag; 52 } 53 54 //条件限制,在某些条件下加锁 55 public Condition newConditionObject(){ 56 return new ConditionObject(); 57 } 58 } 59 60 @Override 61 public void lock() { 62 //设置锁 63 helper.acquire(1); 64 65 } 66 67 @Override 68 public void lockInterruptibly() throws InterruptedException { 69 //中断锁 70 helper.acquireInterruptibly(1); 71 72 } 73 74 @Override 75 public boolean tryLock() { 76 // TODO Auto-generated method stub 77 return helper.tryAcquire(1); 78 } 79 80 @Override 81 public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { 82 // TODO Auto-generated method stub 83 return helper.tryAcquireNanos(1, unit.toNanos(time)); 84 } 85 86 @Override 87 public void unlock() { 88 // 释放锁 89 helper.release(1); 90 } 91 92 @Override 93 public Condition newCondition() { 94 // 在特定条件下给加锁 95 return helper.newConditionObject(); 96 } 97 98 }
模拟锁测试类:
1 /** 2 * 测试类 3 * @author Administrator 4 * 5 */ 6 public class MyLockTest { 7 8 private MyLock lock = new MyLock(); 9 private int m = 0; 10 public int next(){ 11 //开始加锁 12 lock.lock(); 13 try { 14 return m++; 15 } finally { 16 //释放锁 17 lock.unlock(); 18 } 19 } 20 public static void main(String[] args) { 21 MyLockTest t = new MyLockTest(); 22 Thread[] th = new Thread[20]; 23 for (int i = 0; i < th.length; i++) { 24 th[i] = new Thread(()->{ 25 System.out.println("m="+t.next()); 26 }); 27 th[i].start(); 28 } 29 } 30 }
使用JDK提供的可重入性锁(ReentrantLock类):
可重入性:同一个锁当有多个同一资源进行占有的时候,直接分配给这个线程。
1 /** 2 * JDK提供的可重入性互斥锁 3 * @author Administrator 4 * 5 */ 6 public class JDKLock { 7 //使用JDK提供的可重入性锁 8 ReentrantLock lock = new ReentrantLock(); 9 public void a(){ 10 lock.lock(); 11 System.out.println("a"); 12 b(); 13 lock.unlock(); 14 } 15 public void b(){ 16 lock.lock(); 17 System.out.println("b"); 18 lock.unlock(); 19 } 20 public static void main(String[] args) { 21 JDKLock t = new JDKLock(); 22 new Thread(()->{ 23 t.a(); 24 }).start(); 25 } 26 27 }
使用CountDownLatch(int count)类来实现航空公司卖票的示例:
构造器中的计数值(count)实际上就是闭锁需要等待的线程数量,这个值只能被设置一次,而且CountDownLatch函数没有提供任何机制去重新设置这个值。
与CountDownLatch的第一次交互是主线程等待其他线程,主线程必须在启动其他线程后立即调用CountDownLatch.await()方法,这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。其他N个线程必须引用闭锁对象,因为他们需要通知CountDownLatch对象,他们已经完成了各自的任务,这种通知机制是通过CountDownLatch.countDown()方法来完成的,没调用一次这个方法,在构造函数中初始化的count就会减1,所以当N个线程都用了这个方法,count的值就是0,然后主线程就能通过await()方法,恢复执行自己的任务。
1 /** 2 * 模拟航空公司卖票系统 3 * @author Administrator 4 * 5 */ 6 public class FightQueryDemo { 7 8 //公司列表 9 private static List<String> companys = Arrays.asList("东方航空","海南航空","南方航空"); 10 // 11 private static List<String> fightList = new ArrayList<>(); 12 13 public static void main(String[] args) throws InterruptedException { 14 //起始地 15 String oringin ="BJ"; 16 //目的地 17 String dest = "SH"; 18 //根据公司数量创建线程数组 19 Thread[] threads = new Thread[companys.size()]; 20 //创建线程统计计数器 21 CountDownLatch latch = new CountDownLatch(companys.size()); 22 23 for (int i = 0; i < threads.length; i++) { 24 //获取当前公司名称 25 String name = companys.get(i); 26 threads[i] = new Thread(()->{ 27 System.out.printf("%s 查询从%s出发到%s的机票 ",name,oringin,dest); 28 //随机产生票数 29 int val = new Random().nextInt(10); 30 try { 31 TimeUnit.SECONDS.sleep(2); 32 fightList.add(name+"--"+val); 33 System.out.printf("%s 航空公司查询成功 ",name); 34 //计数器减一 35 latch.countDown(); 36 } catch (InterruptedException e) { 37 e.printStackTrace(); 38 } 39 }); 40 threads[i].start(); 41 } 42 //线程执行完以后,唤醒主线程,保证执行完上面的线程,在执行下面的 43 latch.await(); 44 System.out.println("=========查询结果如下============"); 45 fightList.forEach(System.out::println); 46 } 47 }
CyclicBarrier方法实现运动员起跑的示例:
需要所有的子任务都完成时,才执行主任务,这个时候可以选择CyclicBarrier。
基本原理:每个线程执行时都会碰到一个屏障,直到所有线程执行结束,然后屏障便会打开,使所有线程继续往下执行。
在CyclicBarrier的内部定义了一个Lock对象,每当一个线程调用await()方法时,将拦截的线程数加1,然后判断剩余拦截数是否为初始值parties,如果不是,进入Lock对象的条件队列等待,如果是,执行barrierAction对象的Runnable方法,然后将锁的条件队列中的所有线程放入锁等待队列中,这些线程会依次的获取锁,释放锁。
CyclicBarrier的两个构造函数CyclicBarrier(int parties)和CyclicBarrier(int parties,Runnable barrierAction),前者只需要声明需要拦截的线程数即可,而后者还需要定义一个等待所有线程到达屏障优先执行的Runnable对象。
1 /** 2 * 使用CyclicBarrier实现运动员跑道实例 3 * @author Administrator 4 * 5 */ 6 public class RaceDemo { 7 8 public static void main(String[] args) { 9 int num = 8; 10 CyclicBarrier barriers = new CyclicBarrier(num); 11 Thread[] player = new Thread[num]; 12 for (int i = 0; i < player.length; i++) { 13 player[i] = new Thread(()->{ 14 try { 15 TimeUnit.SECONDS.sleep(new Random().nextInt(5)); 16 System.out.println(Thread.currentThread().getName()+"准备好了!"); 17 //设置屏障 18 barriers.await(); 19 } catch (Exception e) { 20 // TODO Auto-generated catch block 21 e.printStackTrace(); 22 } 23 System.out.println("选手"+Thread.currentThread().getName()+"起跑"); 24 },"play["+i+"]"); 25 player[i].start(); 26 } 27 28 } 29 30 }
CountDownLatch和CyclicBarrier的对比:
对于CountDownLatch和CyclicBarrier两个类,我们可以看到CountDownLatch类是一个类似于集合点的概念,很多个线程做完事情后等待其他线程完成,全部线程完成之后再恢复运行。不同的是CountDownLatch类需要自己调用countDown()方法减少一个计数,然后调用await()方法即可,而CyclicBarrier则直接调用await()方法即可。
所以从上面来看,CountDownLatch更倾向于多个线程合作的情况,等你所有的东西都准备好了,我这边就自动执行了,而CyclicBarrier则是我们都在一个地方等你,大家到齐了再一起执行。
以上是关于CAS和AQS的主要内容,如果未能解决你的问题,请参考以下文章
从 synchronized www2015338com到 CAS 和 19908836661AQS