并发编程(三):从AQS到CountDownLatch与ReentrantLock
一、目录
二、AQS简要分析
- 为什么会产生ArrayList、LinkedList、HashMap这些容器?它们底层实现无非都是对数组、链表、树的操作,至于它们的产生,就是因为对编程人员对于数组、链表、树的增删改查操作非常繁琐而提出的解决方案。
- 那为什么会产生AQS呢?谈到同步,大家最容易想到的就是在多线程中如何确保安全的资源共享。那同步队列就是为了解决资源共享的同步容器。像上述容器一样,在顶层就设计好,编程人员只需要调用接口就能轻易实现复杂的资源共享问题。
- 调用自定义同步器的tryAcquire()尝试直接去获取资源,如果成功就返回。
- 没成功,则addWaiter()将线程加入等待队列的尾部,并标记为独享模式。
- acquireQueued()使线程在等待队列中休息,有机会时会去尝试获得资源。获得资源后返回。如果整个过程有中断过返回true,否则返回false。
- 如果线程在等待过程中中断过,它是不响应的。只是获得资源后才再进行自我中断selfInterrupt(),将中断补上。

- tryAcquireShared()尝试获取资源,成功则直接返回。
- 失败则通过 doAcquireShared()进入等待队列,直到被唤醒或者中断并且成功获取资源才返回。
- 不同:独占式是只唤醒后继节点。共享式是唤醒后继,后继还会去唤醒它的后继,从而实现共享。
以上是核心的关于CountDownLatch、ReentrantLock的分析。由于博主研究程度有限,想更深层次研究,请参考:Java并发AQS详解
三、浅谈CountDownLatch
/** * CountDownLatch相当于指令枪或者门闩,所有线程都awit()阻塞在起跑线,只有countDown到state为0,其他线程才能往下运行。 * @author qiuyongAaron */ public class CountDownLatchDemo { private static final int PLAYER_NUM=5;public static void main(String[] args) {
CountDownLatch start=new CountDownLatch(1);
CountDownLatch end =new CountDownLatch(PLAYER_NUM);
Player [] players=new Player[PLAYER_NUM];for(int i=0;i<PLAYER_NUM;i++)
players[i]=new Player(start, end, i);
//指定线程个数的线程池!
ExecutorService exe=Executors.newFixedThreadPool(PLAYER_NUM);
for(Player player:players)
exe.execute(player);System.out.println("比赛开始!");
//比赛开始!
start.countDown();try {
end.await();
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
System.out.println("比赛结束!");
exe.shutdown();
}
}}
class Player implements Runnable{
private CountDownLatch start;
private CountDownLatch end;
private int id;Random random=new Random();
public Player(CountDownLatch start,CountDownLatch end,int id) {
this.start=start;
this.end=end;
this.id=id;
}@Override
public void run() {
try {
//等待比赛开始。
start.await();
TimeUnit.SECONDS.sleep(random.nextInt(10));
System.out.println("Player-"+id+":arrived");
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
//选手-id到达终点,end计数为0结束比赛!
end.countDown();
}
}
}//运行结果:
比赛开始!
Player-3:arrived
Player-4:arrived
Player-0:arrived
Player-1:arrived
Player-2:arrived
比赛结束!
三、谈ReentrantLock
/** * 示例一:同步锁的使用 * reentrantlock用于替代synchronized * 本例中由于m1锁定this,只有m1执行完毕的时候,m2才能执行 * @author qiuyongAaron */ public class ReentrantLockOne { public synchronized void m1(){ for(int i=0;i<10;i++){ try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(i); } }public synchronized void m2(){
System.out.println("hello m2!");
}public static void main(String[] args) {
ReentrantLockOne lock=new ReentrantLockOne();new Thread(()->lock.m1(),"t1").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}new Thread(()->lock.m2(),"t2").start();
}
}
2.2 ReentrantLock实现线程同步-与synchronized作用一致!
/** * 示例二:等价于同步锁 * 使用reentrantlock可以完成同样的功能 * 需要注意的是,必须要必须要必须要手动释放锁(重要的事情说三遍) * 使用syn锁定的话如果遇到异常,jvm会自动释放锁,但是lock必须手动释放锁,因此经常在finally中进行锁的释放 * @author qiuyongAaron */ public class ReentrantLockTwo { ReentrantLock lock =new ReentrantLock(); public void m1(){ try { lock.lock(); for(int i=0;i<10;i++){ TimeUnit.SECONDS.sleep(1); System.out.println(i); } } catch (InterruptedException e) { e.printStackTrace(); }finally{ lock.unlock(); } }public synchronized void m2(){
lock.lock();
System.out.println("hello m2!");
lock.unlock();
}public static void main(String[] args) {
ReentrantLockTwo lock=new ReentrantLockTwo();new Thread(()->lock.m1(),"t1").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}new Thread(()->lock.m2(),"t2").start();
}
}
/** * 示例三:tryLock * 使用reentrantlock可以进行“尝试锁定”tryLock,这样无法锁定,或者在指定时间内无法锁定,线程可以决定是否继续等待 * @author qiuyongAaron */ public class ReentrantLockThree { ReentrantLock lock=new ReentrantLock();public void m1(){
try {
lock.lock();
for(int i=0;i<10;i++){
TimeUnit.SECONDS.sleep(1);
System.out.println(i);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}boolean locked=false;
public void m2(){
try {
lock.tryLock(5,TimeUnit.SECONDS);
System.out.println("m2:"+locked);
} catch (Exception e) {
e.printStackTrace();
}finally{
if(locked) lock.unlock();
}
}public static void main(String[] args) {
ReentrantLockThree lock=new ReentrantLockThree();new Thread(()->lock.m1(),"t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}new Thread(()->lock.m2(),"t2").start();
}
}
2.4 指定公平锁或者抢占式锁
/** * ReentrantLock还可以指定为公平锁 * @author qiuyongAaron */ public class ReentrantLockFive extends Thread{//默认false:为非公平锁 true:公平锁
ReentrantLock lock=new ReentrantLock();@Override
public void run() {
for(int i=0;i<100;i++){
lock.lock();
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"获得锁"+"-"+i);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}}
public static void main(String[] args) {
ReentrantLockFive lock=new ReentrantLockFive();
new Thread(lock,"t1").start();
new Thread(lock,"t2").start();
}
}运行结果:
//非公平锁
t2获得锁-0 t2获得锁-1 t1获得锁-0 t1获得锁-1 t1获得锁-2 t2获得锁-2
//公平锁
t1获得锁-0 t2获得锁-0 t1获得锁-1 t2获得锁-1 t1获得锁-2 t2获得锁-2
/** * 模拟生产者消费者模式-线程之间通信 synchronized-notifyAll/wait * @author qiuyongAaron */ public class MyContainerOne { LinkedList<Integer> list=new LinkedList<Integer>(); static final int MAX=10; int count=0;//生产者线程
public synchronized void put(int i){
while(list.size()==MAX){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(i);
++count;
this.notifyAll();//通知消费者来消费
}//消费者线程
public synchronized int get(){
while(list.size()==0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int num=list.removeFirst();
count--;
this.notifyAll();//通知生产者生产
return num;
}public static void main(String[] args) {
MyContainerOne container=new MyContainerOne();//制造10个消费者
for(int i=0;i<10;i++){
new Thread(()->{
for(int j=0;j<5;j++) System.out.println(container.get());
},
"c"+i).start();
}try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}//制造2个生产者
for(int i=0;i<2;i++){
new Thread(()->{
for(int j=0;j<25;j++) container.put(j);
},
"p"+i).start();
}
}
}
/** * 模拟生产者消费者模式-reentrantLock-awit/signAll * @author qiuyongAaron */ public class MyContainerTwo {LinkedList<Integer> list=new LinkedList<Integer>();
static final int MAX=10;
int count=0;ReentrantLock lock=new ReentrantLock();
Condition producer=lock.newCondition();
Condition consumer=lock.newCondition();//生产者线程
public void put(int i){
try {
lock.lock();
while(list.size()==MAX){
producer.await();
}
list.add(i);
++count;
consumer.signalAll();//通知消费者来消费
} catch (InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock();
}
}//消费者线程
public int get(){
try{
lock.lock();
while(list.size()==0){
consumer.await();
}
int num=list.removeFirst();
count--;
producer.signalAll();//通知生产者生产
return num;
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
return 0;
}public static void main(String[] args) {
MyContainerTwo container=new MyContainerTwo();//制造10个消费者
for(int i=0;i<10;i++){
new Thread(()->{
for(int j=0;j<5;j++) System.out.println(container.get());
},
"c"+i).start();
}try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}//制造2个生产者
for(int i=0;i<2;i++){
new Thread(()->{
for(int j=0;j<25;j++) container.put(j);
},
"p"+i).start();
}
}
}
四、版权声明
作者:邱勇Aaron
出处:http://www.cnblogs.com/qiuyong/
您的支持是对博主深入思考总结的最大鼓励。
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,尊重作者的劳动成果。
参考:马士兵并发编程、并发编程实践
AQS详解:http://www.cnblogs.com/waterystone/p/4920797.html

浙公网安备 33010602011771号