AQS:AbstractQueuedSynchronizer :同步队列器
AQS中维护了一个int 的变量代表共享资源 和一个FIFO线程等待队列,多线程进行资源争抢会被阻塞进入此队列。
队列把它叫做同步等待队列,这个队列的作用是将想要得到这个锁的线程进行保存起来,当获取了资源就进行执行
基于AQS有一个同步组件,叫做ReentrantLock,在这个组件里,state代表获取锁的线程数,Volatile int state 代表共享资源,使用Volatile修饰,保证多线程下的可见性,当state为0时表示当先对象没有线程占用,当state为1时,表示当前对象锁已经被占用,其他线程来加锁会失败,然后线程会被park到等待队列。
当state大于1时,表示重入锁的个数。
需要说的:state的操作都是通过CAS(Compare And Swap)来保证并发修改的安全性。
**AQS的功能主要分为两类:独占和共享。**ReentrantLock 是独占锁,共享锁ReadWriteLock、CountdownLatch。它的所有子类中,要么实现并使用了它的独占功能的api,要么使用了共享锁的功能,而不会同时使用两套api,
线程会首先尝试获取锁,如果失败就将当前线程及等待状态等信息包装成一个node节点加入到同步队列sync queue里。 接着会不断的循环尝试获取锁,条件是当前节点为head的直接后继才会尝试。如果失败就会阻塞自己直到自己被唤醒。而当持有锁的线程释放锁的时候,会唤醒队列中的后继线程。
而Node结点封装的信息有:preNode、NextNode、线程对象Thread和waitStatus。
waitStatus表示结点等待在队列中的状态:
- Signal:表示后续结点需要被唤醒
- condition:线程等待在条件标量队列中
其他线程在进行抢占锁:
- 使用acquire方法
- 使用nonfairTryAcquire不公平的抢占方法判断state的值
- 不为0就是已经被占有,然后判断当前线程是否为占有锁的线程,如果是进行state加1,这是可重入锁的体现,释放锁的时候需要state减1.
- 使用addWaiter方法将自己加入一个FIFO队列中。
- 使用nonfairTryAcquire不公平的抢占方法判断state的值
- 得到的话,会将当前结点设置为head结点,然后置空head结点,方便以后的垃圾回收。
线程释放锁:
- release方法:判断head结点的waitStatu是否为0
- 执行unparkSuccessor方法:state被设置为0
- 去唤醒head结点的后置结点,被唤醒的线程执行刚才被park的位置,执行acquireQueued方法获取资源(进行抢占)
- 执行unparkSuccessor方法:state被设置为0
上述说法都是基于非公平锁实现的,这个是ReentrantLock的默认实现。
非公平锁就是在上面线程释放锁的第三步,是一个抢占型的操作,若是一直别队列后面的线程进行获得资源,会导致线程饥饿的为题。
公平锁在此实现用hasQueuedPredecessors方法判断不能获取资源后将自己放入队列的尾部。
非公平锁和公平锁的区别:
非公平锁性能高于公平锁性能。非公平锁可以减少CPU
唤醒线程的开销,整体的吞吐效率会高点,CPU
也不必取唤醒所有线程,会减少唤起线程的数量
非公平锁性能虽然优于公平锁,但是会存在导致线程饥饿的情况。在最坏的情况下,可能存在某个线程一直获取不到锁。不过相比性能而言,饥饿问题可以暂时忽略,这可能就是ReentrantLock默认创建非公平锁的原因之一了。
Condition:
它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition中的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition
其中AbstractQueueSynchronizer中实现了Condition中的方法,主要对外提供awaite(Object.wait())和signal(Object.notify())调用。
/*
消费者生产者问题:最多生产10个资源,没有资源了唤醒生产者去生产资源,当达到10个资源,阻塞生产者而调用消费者使用资源。
*/
final private int MAX = 10; //最多10个元素
private int count = 0;
private Lock lock = new ReentrantLock() ;
private Condition producer = lock.newCondition() ;
private Condition consumer = lock.newCondition() ;
public void put(T t) {
try {
lock.lock();
while(lists.size() == MAX) { // 想想为什么用while而不是用if ?
producer.await();
}
lists.add(t);
++count;
consumer.signalAll(); //通知消费者线程进行消费
} catch (InterruptedException e) {
e.printStackTrace() ;
} finally {
lock.unlock();
}
}
public T get() {
T t = null;
try {
lock.lock();
while(lists.size() == 0)
consumer.await();
t = lists.removeFirst();
count--;
producer.signalAll(); //通知生产者进行生产
}catch ( InterruptedException e) {
e.printStackTrace() ;
} finally {
lock.unlock();
}
return t;
}
由上可见,Condition可以精确的对多个不同的条件进行控制,wait和notify只能和synchronize关键字进行使用,唤醒一个或者全部的等待队列。
但是Condition需要Lock进行控制,使用的时候需要注意的是在加锁lock后及时进行unlock,底层实现是park和umpark机制,不会产生先唤醒再挂起的死锁,就是不会产生死锁。而wait和notify会产生先唤醒再挂起的死锁。
ASQ组件总结:
ReentrantReadWriteLock
Semaphore
信号量,用来控制同一时间,资源可被访问的线程数量,一般可以用于流量控制。
它的构造函数:
public Semaphore(int var1) {
this.sync = new Semaphore.NonfairSync(var1);
}
public Semaphore(int var1, boolean var2) {
this.sync = (Semaphore.Sync)(var2 ? new Semaphore.FairSync(var1) : new Semaphore.NonfairSync(var1));
}
/**
第一个是共享信号量的个数
第二个是个数和共享变量的竞争是否为公平或者不公平 传入参数Boolean值,false表示不公平。
默认是不公平锁,锁的机制是AQS。
**/
经常使用的方法:
public void acquire() throws InterruptedException {
this.sync.acquireSharedInterruptibly(1);
}
public void release() {
this.sync.releaseShared(1);
}
举个栗子:会看到一次只执行两个
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SemaphoreTest {
private static int count = 10;
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(2,true); //5个信号量
ExecutorService executorService = Executors.newFixedThreadPool(count); //10个线程
for (int i = 0; i < count; i++) {
final int a = i;
executorService.execute(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();//获取信号量
System.out.println(a+"执行");
System.out.println("---------------");
Thread.sleep(3000);//模拟业务流程
semaphore.release();//释放信号量
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
executorService.shutdown();
}
}
CountDownLatch
:它是一个同步辅助器,允许一个或者多个线程一直等待,直到其他线程执行全完毕之后再执行。
CountDwonLatch是通过一个计数器来实现的,计数器的初始值为线程的数量,每当一个线程完成了自己的任务后,计数器的值就会减一,当计数器的值为0时,他表示所有线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行的任务。
它的构造方法会传入一个Count值,用于计数
常用的方法有两个:
public void await() throws InterruptedException {
this.sync.acquireSharedInterruptibly(1);
}
public void countDown() {
this.sync.releaseShared(1);
}
/**
调用第一个方法时候线程会被阻塞。调用countDown方法进行count-1,知道count==0,被阻塞的线程才会继续执行。
*/
CountDownLatch内部定义了一个继承与AQS的静态内部类Sync,操作Count就是操作AQS的state。
import java.util.concurrent.CountDownLatch;
public class TestCountDownLatch {
//一个主线程等待五个子线程执行完毕后继续执行
private static CountDownLatch main_test = new CountDownLatch(1); //一个主线程
private static CountDownLatch sub_test = new CountDownLatch(5); //五个子线程
//主线程
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(new sub_Class(i+1+"")).start();
}
try {
Thread.sleep(2000);
System.out.println("主线程准备启动子线程");
System.out.println();
main_test.countDown();
//等子线程执行完毕
sub_test.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println();
System.out.println("主线程在子线程完毕后完成");
}
//子线程
public static class sub_Class implements Runnable{
String sub_name ; //子线程的名字
public sub_Class(String sub_name) {
this.sub_name = sub_name;
}
@Override
public void run() {
try {
System.out.println("子线程"+sub_name+"正在等待主线程开启子线程");
System.out.println();
//主线程等待子线程完成任务
main_test.await();
System.out.println("子线程"+sub_name+"开始运行");
System.out.println();
//模拟子线程运行时间2秒
Thread.sleep(2000);
System.out.println("子线程"+sub_name+"完成任务");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//子线程完成任务进行计数器减一
sub_test.countDown();
}
}
}
}
CyclicBarrier:循环屏障
是一个同步辅助器,它允许一组一组互相等待的线程直到一组所有的线程都到达有个共同点。
可以设置固定的线程的数量,这些线程必须互相等待,还有就是在所有线程彼此释放之后,这个屏障是可以重复使用的。
它的构造方法可以传入
public CyclicBarrier(int var1) {
this(var1, (Runnable)null);
}
public CyclicBarrier(int var1, Runnable var2) {
this.lock = new ReentrantLock();
this.trip = this.lock.newCondition();
this.generation = new CyclicBarrier.Generation();
if (var1 <= 0) {
throw new IllegalArgumentException();
} else {
this.parties = var1;
this.count = var1;
this.barrierCommand = var2;
}
}
/*
第一个是传入一个数字,代表需要几个线程一起到达,才可以使所有线程取消等待。
第二个是还加了一个线程,意思是所有线程达到屏障后有限制性var2。
*/
/*此方法是用于等待这一组的所有线程都到齐*/
public int await() throws InterruptedException, BrokenBarrierException {
try {
return this.dowait(false, 0L);
} catch (TimeoutException var2) {
throw new Error(var2);
}
}
举一个例子:考试老师收卷子,收完了学生再走
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CyclicBarrierTest {
public static void main(String[] args) {
System.out.println();
System.out.println("老师正在收卷子....");
CyclicBarrier cyclicBarrier = new CyclicBarrier(4, new Runnable() {
@Override
public void run() {
try {
System.out.println();
System.out.println("等待老师收完卷子...");
Thread.sleep(3000);
System.out.println();
System.out.println("收完了.....");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Student student1 = new Student(cyclicBarrier,"小明");
Student student2 = new Student(cyclicBarrier,"小红");
Student student3 = new Student(cyclicBarrier,"小李");
Student student4 = new Student(cyclicBarrier,"小花");
ExecutorService service = Executors.newFixedThreadPool(4);
service.execute(student1);
service.execute(student2);
service.execute(student3);
service.execute(student4);
service.shutdown();
}
static class Student implements Runnable{
CyclicBarrier cyclicBarrier;
String name;
public Student(CyclicBarrier cyclicBarrier, String name) {
this.cyclicBarrier = cyclicBarrier;
this.name = name;
}
@Override
public void run() {
try{
System.out.println();
Thread.sleep(1000);
System.out.println(name+"的卷子正在收....");
cyclicBarrier.await();
System.out.println();
System.out.println(name+"可以走了");
}catch (Exception e){
e.printStackTrace();
}
}
}
}
对于循环屏障:改了线程池的数量和循环屏障设置的数量,效果如下:
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CyclicBarrierTest {
public static void main(String[] args) {
System.out.println("老师正在收卷子....");
System.out.println();
CyclicBarrier cyclicBarrier = new CyclicBarrier(4, new Runnable() {
@Override
public void run() {
try {
System.out.println();
System.out.println("等待老师收完卷子...");
Thread.sleep(3000);
System.out.println("收完了.....");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Student student1 = new Student(cyclicBarrier,"小明");
Student student2 = new Student(cyclicBarrier,"小红");
Student student3 = new Student(cyclicBarrier,"小李");
Student student4 = new Student(cyclicBarrier,"小花");
Student student5 = new Student(cyclicBarrier,"小华");
Student student6 = new Student(cyclicBarrier,"小强");
Student student7 = new Student(cyclicBarrier,"小赵");
Student student8 = new Student(cyclicBarrier,"小陈");
ExecutorService service = Executors.newFixedThreadPool(4);
service.execute(student1);
service.execute(student2);
service.execute(student3);
service.execute(student4);
service.execute(student5);
service.execute(student6);
service.execute(student7);
service.execute(student8);
service.shutdown();
}
static class Student implements Runnable{
CyclicBarrier cyclicBarrier;
String name;
public Student(CyclicBarrier cyclicBarrier, String name) {
this.cyclicBarrier = cyclicBarrier;
this.name = name;
}
@Override
public void run() {
try{
Thread.sleep(1000);
System.out.println(name+"的卷子正在收....");
cyclicBarrier.await();
System.out.println(name+"可以走了");
}catch (Exception e){
e.printStackTrace();
}
}
}
}
读写锁:
读锁和写锁两部分构成,如果读取共享资源的话使用读锁;如果需要修改资源的话使用写锁。
读锁是共享锁:可以多个线程进行读数据;
写锁是排它锁:就是只能有一个线程进行修改数据。
但是在不同的情况下还有读优先锁和写优先锁。
读优先锁:
如图显示:线程A先获取读锁,然后线程B获取写锁失败,线程B被阻塞,然后在阻塞情况下线程C仍然可以获取读锁就可以获取成功。知道线程A和线程C释放读锁资源,线程B才能进行写锁的获取。
写优先锁:
如图显示:线程A先获取写锁,然后线程B获取读锁失败,线程B被阻塞,线程C获取写锁失败阻塞,然后线程A释放写锁,此时线程C优先进行获取写锁。知道没有写锁获取才能线程B进行获取读锁。
需要说明的是:
读优先锁对于读线程并发性好,但也不是没有问题,若是一直存在线程进行读锁获取,那么写优先线程一直会被阻塞,会造成线程饥饿现象。当然对于读锁一样会造成读锁饥饿现象。
解决此问题的方法就是公平读写锁。
公平读写锁比较简单的一种方式是:用队列把获取锁的线程排队,不管是写线程还是读线程都按照先进先出的原则加锁即可,这样读线程仍然可以并发,也不会出现「饥饿」的现象。
总结:
- CountDownLatch 是一个线程等待其他线程, CyclicBarrier 是多个线程互相等待。
- CountDownLatch 的计数是减 1 直到 0,CyclicBarrier 是加 1,直到指定值。
- CountDownLatch 是一次性的, CyclicBarrier 可以循环利用。
- CyclicBarrier 可以在最后一个线程达到屏障之前,选择先执行一个操作。
- Semaphore ,需要拿到许可才能执行,并可以选择公平和非公平模式。