今天我们来试验一下Java5.0 并发编程包的java.util.concurrent.locks子包。这里提供了一些对多线程进行互斥同步控制的类,用以取代之前我们一直使用的synchronized关键字,wait()、notify()、notifyAll()方法。
【Lock】
Lock是一个接口,和关键字synchronized的功能一致,对多线程进行互斥控制!其使用更加面向对象化!对互斥代码段的控制也更加精细化!Lock接口有三个实现类:ReentrantLock,ReentrantReadWriteLock.ReadLock,ReentrantReadWriteLock.WriteLock。后两者都是ReentrantReadWriteLock的子类,这个我们后面再讲。这里先看一个ReentrantLock的例子:
package cn.test;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockTest {
Lock lock = new ReentrantLock();
/**
* 业务方法,需要进行并发互斥控制!我们此处不使用synchronized关键字
*/
public void doBisiness(){
// 加锁,加锁成功的线程进入执行,不成功的线程阻塞在这里!
lock.lock();
try{
// 相应的业务处理方法
}catch(Exception e){
}finally{
// 释放锁,为防止业务方法处理有异常抛出,通常将释放锁的代码写到finally块中
lock.unlock();
}
}
}
这次加锁的过程就是调用一个对象的方法!感觉很舒服,比synchronized更易用并且容易理解!
【ReadWriteLock】
ReadWriteLock也是一个接口,用来实现读写锁的概念(在synchronized的时代,这个是不被支持的)。读写锁的概念,就是读锁与读锁之间不互斥,读锁与写锁,写锁和写锁之间互斥!这种加锁模式,在某些情况下能提高效率!ReentrantReadWriteLock是ReadWriteLock接口的唯一个实现类。其内部提供了两个子类,ReadLock,WriteLock分别来提供读锁和写锁的功能!我们看一个缓存系统的例子,我们需要一个并发环境下的缓存系统,可以缓存多个对象,要求稳定并且性能高效:
package cn.test;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class CacheDemo {
// 将缓存系统设计为单例
private static final CacheDemo INSTANCE = new CacheDemo();
// 内部通过Map的形式来缓存数据
private Map<String, Object> cacheMap = new HashMap<String, Object>();
// 并发互斥通过读写锁进行
private ReadWriteLock lock = new ReentrantReadWriteLock();
private CacheDemo(){
}
public static CacheDemo getInstance(){
return INSTANCE;
}
/**
* 添加缓存的方法,这里是覆盖式添加!
* @param key
* @param value
*/
public void put(String key, Object value){
// 加写锁
lock.writeLock().lock();
try{
cacheMap.put(key, value);
}finally{
lock.writeLock().unlock();
}
}
/**
* 从缓存中读取数据,使用读锁即可,多个线程可以同时从缓存中读取数据!
* @param key
* @return
*/
public Object get(String key){
// 局部变量,无需进行并发互斥控制
Object value = null;
// 加读锁
lock.readLock().lock();
try{
value = cacheMap.get(key);
}finally{
lock.readLock().unlock();
}
return value;
}
/**
* 从缓存中移出数据!需要使用写锁!
* @param key
* @return
*/
public boolean remove(String key){
boolean result = true;
// 删除缓存,加写锁
lock.writeLock().lock();
try{
cacheMap.remove(key);
}catch(Exception e){
result = false;
}finally{
lock.writeLock().unlock();
}
return result;
}
}
利用读写锁实现的缓存效率比利用ReentrantLock或synchronized(可以认为也就是一个写锁关键字)实现的缓存要高!因为这个缓存支持多个线程并发读取!以后当我们在实际情况中,遇到过在读写同时存在的情况下,需要进行并发控制,我们就要考虑到使用读写锁!关于读写锁,再提一点,读写锁有个特性,就是线程如果持有读锁,想升级获取写锁,就必须先释放读锁,再去申请写锁。如果线程持有写锁,可以在持有写锁的情况下,再申请一个读锁,然后再释放写锁!
【Condition】
Condition可以用来实现wait、notify、notifyAll方法的线程间同步通信。但其有增强的地方。我们先看一下wait等方法的使用情况:我们先得到一个对象的监视器,进入同步代码块,发现有些条件限制,我们的线程就要wait在这个对象监视器上,如果我们有100个线程,这100条线程有可能会因为不同的条件限制而要wait,但结果是他们都wait在同一个对象监视器上。一旦有另一个线程处理完了某种条件限制,这种限制的解除会让这100条线程中的5条可以继续执行,但这个线程无法通过notify去精确通知这5条线程,他只能调用notifyAll,去通知所有线程,然后其中95条再重新获取到对象监视器后发现不得不继续wait!!这是wait等方法低效的地方,Condition就对这种情况进行了很好的改进!在使用同一个锁进行互斥控制的线程,可以在不同的Condition对象上进行等待和被唤醒!这就是"多路Condition"的概念!
我们看个这方面的例子,有4条线程,老大,老二,老三,老四,最后实现的效果是:老大执行10次,接着老二再执行20次,接着老三再执行30次,接着老四再执行40次,再循环回去,进行10遍!这里涉及到了顺序执行的问题,我们就利用多路Condition进行精确控制:
package cn.test;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MultiConditionsTest {
public static void main(String[] args) {
final Task task = new Task();
// 启动四个线程,分别执行任务类中的4个任务!
new Thread(new Runnable(){
@Override
public void run() {
for(int j=0;j<10;j++){
task.output1();
}
}}, "First Thread").start();
new Thread(new Runnable(){
@Override
public void run() {
for(int j=0;j<10;j++){
task.output2();
}
}}, "Second Thread").start();
new Thread(new Runnable(){
@Override
public void run() {
for(int j=0;j<10;j++){
task.output3();
}
}}, "Third Thread").start();
new Thread(new Runnable(){
@Override
public void run() {
for(int j=0;j<10;j++){
task.output4();
}
}}, "Forth Thread").start();
}
private static class Task {
private int ctrlOrder = 0;
// 创建一个互斥控制的锁
private Lock lock = new ReentrantLock();
// 调用锁的newCondtion方法,得到一个Condtion对象!
private Condition op1Condition = lock.newCondition();
private Condition op2Condition = lock.newCondition();
private Condition op3Condition = lock.newCondition();
private Condition op4Condition = lock.newCondition();
public void output1() {
lock.lock();
try {
// 使用Condition也会产生假醒现象!所以此处要用while循环进行判断!
while (ctrlOrder % 4 != 0) {
// 调用Condtion对象的await方法,进行等待
op1Condition.await();
}
// 执行相应的业务逻辑
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()
+ " out put " + i);
}
ctrlOrder++;
// 通过调用特定的Condition的signal来唤醒在该Condition对象上等待的线程!精确唤醒!
op2Condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void output2() {
lock.lock();
try {
// 使用Condition也会产生假醒现象!所以此处要用while循环进行判断!
while (ctrlOrder % 4 != 1) {
// 调用Condtion对象的await方法,进行等待
op2Condition.await();
}
// 执行相应的业务逻辑
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()
+ " out put " + i);
}
ctrlOrder++;
// 通过调用特定的Condition的signal来唤醒在该Condition对象上等待的线程!精确唤醒!
op3Condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void output3() {
lock.lock();
try {
// 使用Condition也会产生假醒现象!所以此处要用while循环进行判断!
while (ctrlOrder % 4 != 2) {
// 调用Condtion对象的await方法,进行等待
op3Condition.await();
}
// 执行相应的业务逻辑
for (int i = 0; i < 30; i++) {
System.out.println(Thread.currentThread().getName()
+ " out put " + i);
}
ctrlOrder++;
// 通过调用特定的Condition的signal来唤醒在该Condition对象上等待的线程!精确唤醒!
op4Condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void output4() {
lock.lock();
try {
// 使用Condition也会产生假醒现象!所以此处要用while循环进行判断!
while (ctrlOrder % 4 != 3) {
// 调用Condtion对象的await方法,进行等待
op4Condition.await();
}
// 执行相应的业务逻辑
for (int i = 0; i < 40; i++) {
System.out.println(Thread.currentThread().getName()
+ " out put " + i);
}
ctrlOrder++;
// 通过调用特定的Condition的signal来唤醒在该Condition对象上等待的线程!精确唤醒!
op1Condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
输出结果为:
First Thread out put 0
First Thread out put 1
First Thread out put 2
First Thread out put 3
First Thread out put 4
First Thread out put 5
First Thread out put 6
First Thread out put 7
First Thread out put 8
First Thread out put 9
Second Thread out put 0
Second Thread out put 1
Second Thread out put 2
Second Thread out put 3
Second Thread out put 4
Second Thread out put 5
Second Thread out put 6
Second Thread out put 7
Second Thread out put 8
Second Thread out put 9
Second Thread out put 10
Second Thread out put 11
Second Thread out put 12
Second Thread out put 13
Second Thread out put 14
Second Thread out put 15
Second Thread out put 16
Second Thread out put 17
Second Thread out put 18
Second Thread out put 19
Third Thread out put 0
Third Thread out put 1
Third Thread out put 2
Third Thread out put 3
Third Thread out put 4
Third Thread out put 5
Third Thread out put 6
Third Thread out put 7
Third Thread out put 8
Third Thread out put 9
Third Thread out put 10
Third Thread out put 11
Third Thread out put 12
Third Thread out put 13
Third Thread out put 14
Third Thread out put 15
Third Thread out put 16
Third Thread out put 17
Third Thread out put 18
Third Thread out put 19
Third Thread out put 20
Third Thread out put 21
Third Thread out put 22
Third Thread out put 23
Third Thread out put 24
Third Thread out put 25
Third Thread out put 26
Third Thread out put 27
Third Thread out put 28
Third Thread out put 29
Forth Thread out put 0
Forth Thread out put 1
Forth Thread out put 2
Forth Thread out put 3
Forth Thread out put 4
Forth Thread out put 5
Forth Thread out put 6
Forth Thread out put 7
Forth Thread out put 8
Forth Thread out put 9
Forth Thread out put 10
Forth Thread out put 11
Forth Thread out put 12
Forth Thread out put 13
Forth Thread out put 14
Forth Thread out put 15
Forth Thread out put 16
Forth Thread out put 17
Forth Thread out put 18
Forth Thread out put 19
Forth Thread out put 20
Forth Thread out put 21
Forth Thread out put 22
Forth Thread out put 23
Forth Thread out put 24
Forth Thread out put 25
Forth Thread out put 26
Forth Thread out put 27
Forth Thread out put 28
Forth Thread out put 29
Forth Thread out put 30
Forth Thread out put 31
Forth Thread out put 32
Forth Thread out put 33
Forth Thread out put 34
Forth Thread out put 35
Forth Thread out put 36
Forth Thread out put 37
Forth Thread out put 38
Forth Thread out put 39
First Thread out put 0
First Thread out put 1
First Thread out put 2
First Thread out put 3
First Thread out put 4
First Thread out put 5
First Thread out put 6
First Thread out put 7
First Thread out put 8
First Thread out put 9
上述控制通过synchronized也能实现,但无法做到同一个锁上,多路Condition精确控制的效果!
今天主要写了关于Lock和Condition一些内容,既然Java5.0中提供了这么好的东西,我们在以后写代码中,如果需要线程的互斥同步控制,就不要再使用老方式了,采用这个新的更高效易用的方式吧!