一. lock可以代替synchronized关键字实现互斥功能。使用方法如下:
Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); }
需要注意的是。
1.需要互斥的一个或多个方法要使用同一个互斥锁。
2.在被锁包含的代码块中,要使用finally块将锁释放。
二. Condition的await方法(注意不是wait方法)可以替换传统通信中的wait方法,对应的signal方法替换notify。
在传统通信的条件判断时,我们会用while而不是if做条件判断,是为了虚假唤醒。那么什么是虚假唤醒呢?
虚假唤醒即:如:我们要求AB方法按顺序执行,有5个线程执行A,5个线程执行B,如果某时刻全部A等待,当其中A1被唤醒,并执行完代码后,会调用notify方法,其本意是唤醒B模块执行线程,但是由于AB公用一个锁,所以可能将A唤醒,即唤醒了不该执行的代码,这就是虚假唤醒。
所以我们使用while条件,即使被唤醒了,我们还会做一次条件判读,这样被虚假唤醒的代码将再一次等待。
这就要求程序员来控制避免虚假唤醒带来的错误。而Lock和Condition的帮我们解决了这个问题。一个锁内部可以有多个Condition.那么同一个锁内可以多个condition实现模块
之间的切换,如上面的例子中A1再执行完之后,通过B对应的Condtion.signal只可以唤醒B对应的线程。我们可以看看Condition的API中的例子,阻塞队列的简单实现:
首先我们看看传统的通信技术实现简单的阻塞队列,有何弊端?
package com.lipeng; import java.util.Random; public class BoundedBuffer1 <T>{ private Object[] objs=new Object[100]; private int length; private int putIndex=0;//存指针 private int getIndex=0;//取指针 /** * 存放元素,从头到尾,再反复从头到尾 * @param t */ public synchronized void put(T t) { //如果已经放满了,就等待。 while(length==objs.length)//如果N个线程在这儿等待, 其中一个线程被唤醒后,执行下面的代码,35行this.notify本意是唤醒取线程取数据,但其实可能唤醒存线程。 { try { this.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } objs[putIndex]=t; //在队尾插入元素 length++; //长度加1, putIndex++; if(putIndex==objs.length) { //注意不是放满了才从起始处放,而是存放指针到队尾了再从头开始。 putIndex=0; } this.notify(); } /** * 取元素,从头到尾取,在如此反复。 * @return */ public synchronized T get() { while(length==0) { try { this.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } T t=(T) objs[getIndex]; length--; getIndex++; if(getIndex==objs.length) { getIndex=0; } this.notify(); return t; } public static void main(String[] args) { final BoundedBuffer1<Integer> bb=new BoundedBuffer1<Integer>(); Runnable getRun=new Runnable() { @Override public void run() { for(int i=0;i<10;i++) { synchronized (bb) { //这里加synchronized只是为了让读取数据和打印数据保持完整性,做演示用用 Integer data=bb.get(); System.out.println(Thread.currentThread().getName()+" 读取元素------ "+data); } } } }; Runnable putRun=new Runnable() { @Override public void run() { for(int i=0;i<10;i++) { synchronized (bb) {//这里加synchronized只是为了让存放数据和打印数据保持完整性,做演示用用 Integer data=new Random().nextInt(100); bb.put(data); System.out.println(Thread.currentThread().getName()+" 放入--------------------------- "+data); } } } }; System.out.println("***********************"); for(int i=0;i<10;i++) { new Thread(getRun).start(); new Thread(putRun).start(); } } }
我们再来看Condition是如何帮我们实现的?
package com.lipeng; import java.util.Random; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class BoundedBuffer2 <T>{ private Object[] objs=new Object[100]; private int length; private int putIndex=0;//存指针 private int getIndex=0;//取指针 private Lock lock=new ReentrantLock(); private Condition putCon=lock.newCondition();//存放条件 private Condition getCon=lock.newCondition();// 取条件 /** * 存放元素,从头到尾,再反复从头到尾 * @param t */ public void put(T t) { try { lock.lock(); //如果已经放满了,就等待。 while(length==objs.length)//如果N个线程在这儿等待, 其中一个线程被唤醒后,执行下面的代码,35行this.notify本意是唤醒取线程取数据,但其实可能唤醒存线程。 { try { putCon.await(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } objs[putIndex]=t; //在队尾插入元素 length++; //长度加1, putIndex++; if(putIndex==objs.length) { //注意不是放满了才从起始处放,而是存放指针到队尾了再从头开始。 putIndex=0; } getCon.signal(); System.out.println(Thread.currentThread().getName()+" 放入--------------------------- "+t); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ lock.unlock(); } } /** * 取元素,从头到尾取,在如此反复。 * @return */ public T get() { try { lock.lock(); while(length==0) { try { getCon.await(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } T t=(T) objs[getIndex]; length--; getIndex++; if(getIndex==objs.length) { getIndex=0; } putCon.signal(); System.out.println(Thread.currentThread().getName()+" 读取元素------ "+t); return t; } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); return null; }finally{ lock.unlock(); } } public static void main(String[] args) { final BoundedBuffer2<Integer> bb=new BoundedBuffer2<Integer>(); Runnable getRun=new Runnable() { @Override public void run() { for(int i=0;i<10;i++) { bb.get(); } } }; Runnable putRun=new Runnable() { @Override public void run() { for(int i=0;i<10;i++) { Integer data=new Random().nextInt(100); bb.put(data); } } }; System.out.println("***********************"); for(int i=0;i<10;i++) { new Thread(getRun).start(); new Thread(putRun).start(); } } }
注意事项:
Lock(包括读写锁)+Condition看起来更加面向对象,也似乎提高了性能*(因为我没验证过,哈哈)也更加严谨,但我认为如果传统的通信方法够用,没必要使用它。