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的主要内容,如果未能解决你的问题,请参考以下文章

CAS和AQS

AQS和CAS

06 CAS的原理和AQS

从 synchronized www2015338com到 CAS 和 19908836661AQS

从 synchronized 到 CAS 和 AQS - 彻底弄懂 Java 各种并发锁

CAS和AQS一文搞懂