您的位置:首页 > 编程语言 > Java开发

Java多线程与并发应用-(9)-锁lock+条件阻塞conditon实现线程同步通信

2015-05-02 11:02 621 查看
一. 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看起来更加面向对象,也似乎提高了性能*(因为我没验证过,哈哈)也更加严谨,但我认为如果传统的通信方法够用,没必要使用它。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐