AbstractQueuedSynchronizer是一个在并发开发中常用的抽象类,但是可能很多童鞋没有注意到因为我们间接用的都是它的子类;
如下这些(是不是很眼熟):
java.util.concurrent.CountDownLatch
java.util.concurrent.locks.ReentrantLock
java.util.concurrent.FutureTask
java.util.concurrent.Semaphore
这些类的作者是鼎鼎有名的Doug Lea,JAVA集合框架中随处可以看到他的身影, 佩服!
这些类用到了AbstractQueuedSynchronizer的子类(作为以上四个类的内部类Sync),这里涉及涉及模式中的“适配器模式”,大神的代码里处处有各种设计模式的身影,看源码再集合各种设计模式的讲解是一个很好的学习方法;
重点:
需要注意的是AbstractQueuedSynchronizer实现锁的机制是通过维护原子变量state的状态来完成的,跟synchronized实现的机制不一样;
可参看连接 http://www.cnblogs.com/maowang1991/archive/2013/04/15/3023236.html
CountDownLatch
场景
这是一个在多线程开发中控制线程执行流程的,比如在程序中开启4个线程来处理用户上传的若干图片,等这四个图片处理线程都处理完毕后要返回给用户处理的结果信息,就可以用到CountDownLatch;
接下来我们看看它的用法,直接上代码
如果不用CountDownLatch
public class Pic {
private String picInfo;
public Pic(String picInfo) {
this.picInfo = picInfo;
}
public void doPic() {
new doPicThread().start();
new doPicThread().start();
new doPicThread().start();
new doPicThread().start();
System.out.println("我处理完了");
}
class doPicThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 我拿到了:"+picInfo);
}
}
public static void main(String[] args) {
new Pic("iiii").doPic();
}
}
结果
可以看到这样写的话是很有问题的,虽然代码中“我处理完了”是在启动所有线程后,但是线程的执行顺序由操作系统来选择;
比较好的处理方案就是引进CountDownLatch类来进行处理,在所有子线程没有处理前让主线程进入等待状态,等所有子线程执行
完了后才继续执行相关逻辑;
代码改进
public class Pic {
private String picInfo;
private CountDownLatch latch = new CountDownLatch(4);//有多少个线程就实例化几个Latch
public Pic(String picInfo) {
this.picInfo = picInfo;
}
public void doPic() {
new doPicThread(latch).start();//需要把相同的latch传入到线程类中
new doPicThread(latch).start();
new doPicThread(latch).start();
new doPicThread(latch).start();
try {
latch.await();//主线程调用latch的await方法,这个方法会让当前线程中止往下执行,直到每个线程中都执行完countDown()方法
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我处理完了");
}
class doPicThread extends Thread {
private CountDownLatch l;
public doPicThread(CountDownLatch l) {
this.l = l;
}
public void setL(CountDownLatch l) {
this.l = l;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " 我拿到了:"
+ picInfo);
} catch (Exception e) {
// TODO: handle exception
} finally {
l.countDown();//注意这里countDown极力推荐写在finally中,因为如果由于某种异常这个方法没有执行那么主线程就要一直等待下去了
}
}
}
public static void main(String[] args) {
new Pic("iiii").doPic();
}
}
结果
原理
简单理解:在我们实例化CountDownLatch对象A的时候,需要传入一个int类型的参数a,这个参数用于设置AbstractQueuedSynchronizer类的state属性值(共有变量方式实现锁机制),当每个线程中调用A对象的countDown方法时这个值就会减少一,直至减少到0则处于interupt状态的主线程就会被唤醒。
ReentrantLock
场景
需要保证不同线程有序的执行一段代码(跟synchronized功能类似)即可使用ReentrantLock(可重入锁);
原理
以下是我的理解(看了几天源码也翻了很多资料,发现这个类代码简介但难懂):
1、调用ReentrantLock实例的lock方法时,当前线程就会去获得锁(尝试通过CAS算法修改内部类Sync父类AQS的state属性值由0边为1),如果修改成功即表示当前线程获得了锁,设置当前线程为拥有者
2、如果通过CAS算法没有修改成功,则当前线程通过自旋方式(循环)继续来获得锁(修改原子变量state的值)
3、。。。。。。