一. 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看起来更加面向对象,也似乎提高了性能*(因为我没验证过,哈哈)也更加严谨,但我认为如果传统的通信方法够用,没必要使用它。