JUC并发编程(三)之AQS
AQS原理
AbstractQueuedSynchronizer
先大致讲一下工作原理:AQS内部主要维护了一个Node类型的链表,用于储存排队的线程信息,当有新的需要阻塞的线程进来时一般创建一个node对象,加入到链表的尾部,当链表中首节点释放资源时调用LockSupport的unpark去唤醒等待的线程。
当一个线程进来以后,抢到资源会把state由0改为1,其他线程在state为1时,就会进入阻塞,并添加进链表尾部,等待被唤醒。
CountDownLatch
CountDownLatch可以使一个或者多个线程等待,其他线程各自执行完毕后再执行。
CountDownLatch 定义了一个计数器,和一个阻塞队列, 当计数器的值递减为0之前,阻塞队列里面的线程处于挂起状态,当计数器递减到0时会唤醒阻塞队列所有线程。
countDownLatch.await();//是当前线程等待,进入阻塞队列
countDownLatch.countDown();//state值-1,state为0时唤醒线程
CountDownLatch可以解决那些一个或者多个线程在执行之前必须依赖于某些必要的前提业务先执行的场景。
//用于聚合所有的统计指标
private static Map map=new HashMap();
//创建计数器,这里需要统计4个指标
private static CountDownLatch countDownLatch=new CountDownLatch(4);
public static void main(String[] args) {
//记录开始时间
long startTime=System.currentTimeMillis();
Thread countUserThread=new Thread(new Runnable() {
public void run() {
try {
System.out.println("正在统计新增用户数量");
Thread.sleep(3000);//任务执行需要3秒
map.put("userNumber",1);//保存结果值
countDownLatch.countDown();//标记已经完成一个任务
System.out.println("统计新增用户数量完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread countOrderThread=new Thread(new Runnable() {
public void run() {
try {
System.out.println("正在统计订单数量");
Thread.sleep(3000);//任务执行需要3秒
map.put("countOrder",2);//保存结果值
countDownLatch.countDown();//标记已经完成一个任务
System.out.println("统计订单数量完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread countGoodsThread=new Thread(new Runnable() {
public void run() {
try {
System.out.println("正在商品销量");
Thread.sleep(3000);//任务执行需要3秒
map.put("countGoods",3);//保存结果值
countDownLatch.countDown();//标记已经完成一个任务
System.out.println("统计商品销量完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread countmoneyThread=new Thread(new Runnable() {
public void run() {
try {
System.out.println("正在总销售额");
Thread.sleep(3000);//任务执行需要3秒
map.put("countmoney",4);//保存结果值
countDownLatch.countDown();//标记已经完成一个任务
System.out.println("统计销售额完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//启动子线程执行任务
countUserThread.start();
countGoodsThread.start();
countOrderThread.start();
countmoneyThread.start();
try {
//主线程等待所有统计指标执行完毕
countDownLatch.await();
long endTime=System.currentTimeMillis();//记录结束时间
System.out.println("------统计指标全部完成--------");
System.out.println("统计结果为:"+map.toString());
System.out.println("任务总执行时间为"+(endTime-startTime)/1000+"秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
CycliBarriar
在Java中CycliBarriar和CountdownLatch有什么区别?CyclicBarrier可以重复使用,而CountdownLatch不能重复使用。
CountDownLatch其实可以把它看作一个计数器,只不过这个计数器的操作是原子操作,就是同时只能有一个线程去减这个计数器里面的值。可以向CountDownLatch对象设置一个初始的数字作为计数值,任何调用这个对象上的await()方法都会阻塞,直到这个计数器的计数值被其他的线程减为0为止。所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。
public class CycliBarryDemo extends Thread{
public static void main(String[] args) {
CyclicBarrier cyclicBarrier=new CyclicBarrier(3,new CycliBarryDemo());
for(int i=0;i<3;i++){
new ThreadDemo(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始了");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"继续开始");
}
}.start();
}
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"总计");
}
}
这个执行结果会发现:
执行统计的业务后,其他业务会继续在执行,这与CountDownLatch有所不同
Semaphore
Semaphore 是 synchronized 的加强版,作用是控制线程的并发数量。是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源
应用场景
Semaphore可以用于做流量控制,特别公用资源有限的应用场景,比如数据库连接。假如有一个需求,要读取几万个文件的数据,因为都是IO密集型任务,我们可以启动几十个线程并发的读取,但是如果读到内存后,还需要存储到数据库中,而数据库的连接数只有10个,这时我们必须控制只有十个线程同时获取数据库连接保存数据,否则会报错无法获取数据库连接。这个时候,我们就可以使用Semaphore来做流控
public class RemaphoreDemo extends Thread{
static Semaphore semaphore=new Semaphore(5);
public static void main(String[] args) {
for(int i=0;i<10;i++){
RemaphoreDemo demo = new RemaphoreDemo();
demo.setName("线程"+i);
demo.start();
}
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"---"+"获得许可");
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
semaphore.release();
System.out.println(Thread.currentThread().getName()+"---"+"离开了,剩余许可数量"+semaphore.availablePermits());
}
}