Java多线程:新特性—显式锁和条件变量

显式锁


在Java5.0之前,在协调共享对象的访问时候可以使用的机制只有synchronized和volatile。

Java5.0增加了一种新的机制:ReentrantLock。称为显式锁,相对于对象的内置锁而言。

这个专门提供的锁对象,可以方便的实现资源的封锁,用来控制对竞争资源并发访问的控制。

这些内容主要集中在java.util.concurrent.locks包下面,里面有三个重要的接口:ConditionLockReadWriteLock

1
2
3
4
5
6
Condition
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待set(wait-set)。
Lock
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
ReadWriteLock
ReadWriteLock 维护了一对相关的锁定,一个用于只读操作,另一个用于写入操作。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/***
* 显式锁测试
*
*/
public class LockTest extends Thread{
private Lock lock;
public LockTest(Lock lock) {
this.lock = lock;
}
@Override
public void run(){
lock.lock();
try{
for(int i=0;i<2;i++){
System.out.println(Thread.currentThread().getName());
Thread.sleep(1000);
}
}catch(Exception e){
}finally{
lock.unlock();
}
}
public static void main(String[] args) {
Lock lock = new ReentrantLock();
LockTest[] array = new LockTest[2];
for (int i = 0; i < array.length; i++) {
array[i] = new LockTest(lock);
}
for (int i = 0; i < array.length; i++) {
array[i].start();
}
}
}

输出结果:

1
2
3
4
Thread-0
Thread-0
Thread-1
Thread-1

从上面的输出可以看到,利用锁对象太方便了,比直接在某个不知情的对象上用锁清晰多了。

但一定要注意的是,在获取了锁对象后,用完后应该尽快释放锁,以便别的等待该锁的线程有机会去执行

为了可以释放锁,经典写法如下:

1
2
3
4
5
6
7
8
lock.lock();
try{
}catch(Exception e){
}finally{
lock.unlock();
}

当然,Java提供了读写锁ReadWriteLock,在读的地方使用读锁(可以多个线程访问),在写的地方使用写锁(只有一个线程访问),灵活控制,在一定程度上可以提高程序的执行效率。

在实际开发中,最好在能用读写锁的情况下使用读写锁,而不要用普通锁,以求更好的性能。

条件变量


条件变量是Java5.0线程中很重要的一个概念,顾名思义,条件变量就是表示条件的一种变量。但是必须说明,这里的条件是没有实际含义的,仅仅是个标记而已,并且条件的含义往往通过代码来赋予其含义。

条件变量都实现了java.util.concurrent.locks.Condition接口,条件变量的实例化是通过一个Lock对象上调用newCondition()方法来获取的,这样,条件就和一个锁对象绑定起来了。

因此,Java中的条件变量只能和锁配合使用,来控制并发程序访问竞争资源的安全

条件变量的出现是为了更精细控制线程等待与唤醒,在Java5.0之前,线程的等待与唤醒依靠的是Object对象的wait()notify()/notifyAll()方法,这样的处理不够精细。

注意:这两套机制不能混合使用,这是两套独立的机制。

而在Java5.0中,一个锁可以有多个条件,每个条件上可以有多个线程等待,通过调用await()方法,可以让线程在该条件下等待。

当调用signalAll()方法,又可以唤醒该条件下的等待的线程。有关Condition接口的API可以具体参考JavaAPI文档。