一、AQS
AQS全称abstractQueuedSynchronizer,它是一个抽象类,队列同步器,常用的实现有ReentrantLock和ReadWriteLock。
作用:构建锁、释放锁、同步队列。
参数:
我们首先要了解AQS中的重要参数:
head:记录队列中头部节点;
tail:记录队列中尾部节点;
state:记录队列中等待访问线程数量,通过volatile关键字修饰,保证该参数对所有参数可见且有序(这里的有序是指的是在内存上有序访问);
note内部类:一个双向队列;
thread:当前占用资源线程;
实现方式:
AQS采用的是乐观锁,乐观锁其实不是真实的锁,它只是在代码层面通过CAS算法或对比版本号来防止并发,实现锁的作用。
1、当一个线程请求资源时,首先要获取锁资源,就是通过CAS算法,判断内存上的state值是否为0,也就是队列中是否有阻塞线程,如果没有,那么就可以直接将新值赋值到内存地址上,然后进行创建note节点,head、tail都指向该note节点,记录当前线程,state+1操作等等;如果有阻塞队列,也不会直接存入队列,而是会再次尝试获取一次锁,如果这次还获取失败,那就存入队列,这就实现了加锁的过程。
2、当一个线程操作完资源以后,如果存在head节点,并且head指向节点中的waitState不为0,则唤醒head指向的节点,将AQS中的state设置为0,head指向的节点中的waitState的值设为0,唤醒head节点对应线程,重新通过CAS算法竞争锁资源,这就是释放锁的过程。
通过加锁和释放锁来实现队列同步。
二、CAS
CAS全称compareAndSwap,它是一种算法。
作用:可以实现加锁效果,它仅仅是实现了和加锁一样的效果,它并不是一种锁,它可以防止并发访问出错。
实现:线程请求内存资源,资源不加锁,哪个线程都可以取,取出来怎么操作都可以,写入内存时再进行比较,比较的内容就是从内存中取出的数据和此时内存中的数据是否一样,如果一样就认为在这个时间段没有其他线程访问,可以安全写入;如果不一样,则取回新数据重新操作,然后重新写入到内存,这个时候写入内存时重新比较,如果又不一样,表明在这个时间段被其他线程修改了,这种情况就会再去取回数据再进行操作和比较,这就是我们所说的自旋,直到一样时才写入。
扩展:这里有个非常著名的ABA问题,就是我第一个线程取出内存数据为A,操作完以后变成数据B准备写入到内存,这个期间第二个线程把它修改成了B,第三个线程又把它改成了A,这个时候我们写入时发现还是A,会认为这个时间段没有其他线程操作,这就出现了ABA问题。
解决办法:加一个版本号就能解决这个问题,我们在每一次修改值的时候加一个版本号,如果当前的版本号和写入之前的版本号不一致的话,就可以知道在这个时间段有其他线程访问过这个值。