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的机票\n",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 航空公司查询成功\n",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则是我们都在一个地方等你,大家到齐了再一起执行。
使用Semaphore实现停车场控制车辆进出的示例:
Semaphore解决有限的资源共享问题。
1 /**
2 * 使用Semaphore实现停车场功能
3 * @author Administrator
4 *
5 */
6 public class CarDemo {
7
8 public static void main(String[] args) {
9 //创建seamphore
10 Semaphore sp = new Semaphore(5);
11 //请求许可
12 Thread[] carThread = new Thread[10];
13 for (int i = 0; i < carThread.length; i++) {
14 carThread[i] = new Thread(()->{
15 //请求许可
16 try {
17 TimeUnit.SECONDS.sleep(2);
18 sp.acquire();
19 System.out.println(Thread.currentThread().getName()+"可以进入停车场");
20 } catch (InterruptedException e) {
21 e.printStackTrace();
22 }
23 //使用资源(占用资源一段时间)
24 try {
25 int val = new Random().nextInt(10);
26 TimeUnit.SECONDS.sleep(val);
27 System.out.println(Thread.currentThread().getName()+"停留了"+val+"秒!");
28 } catch (InterruptedException e) {
29 e.printStackTrace();
30 }
31 //释放资源
32 try {
33 sp.release();
34 System.out.println(Thread.currentThread().getName()+"离开了停车场!");
35 } catch (Exception e) {
36 e.printStackTrace();
37 }
38 },"carThread["+i+"]");
39 carThread[i].start();
40 }
41 }
42 }