Java高级之Lock&Condition实现线程同步通信

1、Lock简介

首先Java1.5中增加了并发库的包,包中增加了锁的概念即java.util.concurrent.locks包。Lock比传统线程模式中的synchronized方式更加面向对象,它与生活中的锁类似,锁本身也应该是一个对象。两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个lock对象。锁是上在代表要操作的资源的类的内部方法中,而不是线程代码中。锁定使用Lock的lock()方法,释放锁使用的是Lock的unLock()方法。其中的lock()与unLock()是成对出现的。

public class LockDemo {

public static void main(String[] args) {
final Outputer outputer = new Outputer();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
outputer.output("wangjian");
}
}
}).start();

new Thread(new Runnable() {
@Override
public void run() {
while (true) {
outputer.output("linxiulian");
}
}
}).start();

}

public static class Outputer{
Lock lock;

public Outputer(){
lock = new ReentrantLock();
}

public void output(String name){
try {
lock.lock();
for(int i=0;i<name.length();i++){
System.out.print(name.charAt(i));
}
System.out.println("");
} finally{
lock.unlock();
}

}
}

}


注意  lock.lock()与 lock.unlock();成对出现
        需要对上锁的代码try,并在finally中执行释放锁的操作,这样避免了无论在正常退出还是非正常退出都可以得到锁的正常释放,从而不影响其他的功能。

2、Lock之读写锁

Lock分为读写锁即读锁与写锁。多个读锁之间不互斥,而读锁与写锁、写锁与写锁之间都是互斥的。这些互斥是jvm自己控制的(即这些锁之间是如何互斥的内部操作是Java虚拟机自己去完成的),你只要上好相应的锁即可。只要在读的时候上读锁,写的时候上写锁,就没有问题。所以这里Lock要比传统的synchronized功能丰富。

public class LockDemo2 {

public static void main(String[] args) {
final DataInfo dataInfo = new DataInfo();

for(int i=0;i<3;i++){
final int task = i;
new Thread(new Runnable() {
@Override
public void run() {
if(task==0){
dataInfo.write("wangjian");
}else if(task ==1){
dataInfo.write("linxiulian");
}else {
dataInfo.write("wjlxl");
}
}
}).start();
}

for(int i=0;i<3;i++){
final int task = i;
new Thread(new Runnable() {
@Override
public void run() {
dataInfo.read();
}
}).start();
}
}

public static class DataInfo{

public String mData;

ReadWriteLock mLock;//读写锁

public DataInfo(){
mLock = new ReentrantReadWriteLock();
}

public void read(){
mLock.readLock().lock();
try {
Thread.sleep(200);
System.out.println(Thread.currentThread().getName() + " read data:"+ mData);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
mLock.readLock().unlock();
}
}

public void write(String data){
mLock.writeLock().lock();
try {
Thread.sleep(200);
this.mData = data;
System.out.println(Thread.currentThread().getName() + " write data:" + mData);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
mLock.writeLock().unlock();
}
}
}
}


以下是做一个缓存系统的demo(所谓缓存系统:就是缓存中有直接用缓存,没有就在去查数据库),用来体现lock的读写锁的妙处。

public class LockDemo3 {

private static final Map<String,Object> mCache = new HashMap<String, Object>();

public static void main(String[] args) {
final CacheUtil cacheUtil = new CacheUtil();

for(int i=0;i<3;i++){
new Thread(new Runnable() {
@Override
public void run() {
cacheUtil.getData("key");
}
}).start();
}

}

public static class CacheUtil{

ReadWriteLock  mLock = null;

public CacheUtil(){
mLock = new ReentrantReadWriteLock();
}

public Object getData(String key){
mLock.readLock().lock();
Object data = null;
try{
data = mCache.get(key);
if(data == null){
mLock.readLock().unlock();
mLock.writeLock().lock();
try{
if(data == null){//当第一个写线程进入之后写完了数据,就有了缓存,如果其他的被挡在锁外面的线程进入时候需要判断
data = "resulet";//实际上去查数据库
                                                       mCache.put(key,data);
}
}finally{
mLock.writeLock().unlock();
}
mLock.readLock().lock();
}
}finally{
mLock.readLock().unlock();
}
return data;
}

}
}



3、Condition之线程通信

Conditon的功能就类似与传统的线程技术中的wait方法和notif方法,其中wait与notif是在synchronized代码块或者方法中结合使用已达到线程通信的目的。在Java1.5之后并发库中增加了Condition类,该类是与Lock类结合使用的,即condition的await方法和signal方法必须是在lock与unlock方法之间。

Condition类与wait、notif的区别在于,wait、notif的依附对象必须是同一个锁对象,即假如有多个方法通过synchronized关键字互斥,那么wait、notif阻塞唤醒都是针对于一个对象锁而言的,不能够在仔细的区分唤醒条件。而condition可以在同一个锁lock下建立多个阻塞唤醒条件,这些条件之间互不干涉,能够比wait、notif更加细致丰富。

无论是synchronized还是lock关键字,在线程同步中,需要多次的、谨慎的判断线程是否唤醒,要避免线程是假唤醒,那么在当前同步代码中需要反复的检查唤醒条件,所以此时最好选用while,而不是if,这个点会在下面的列子中使用到。


下面是一个阻塞队列的官方例子,首选介绍一下什么是阻塞对列,所谓的阻塞对列就相当于一个固定容量为n的容器(可以理解为数组),往里面放的时候,按照0-(n-1)位置放,如果容器放满了,则阻塞接下来放的线程,直到有空位置为止,如果当数据放到n-1的位置时,此时再放数据就放到0的位置上,依次循环。当往外去取的时候,也是按照0-(n-1)的位置依次去取,如果取到n-1位置的时候,会循环跳到0的位置上,如果数据为取空了,则取的线程阻塞直到有数据为止。demo1如下:

public class ConditionDemo1 {

public static void main(String[] args) {

}

public static class BlockBuffer{

public Lock mLock;

//这个地方生成两个Condition,是让取的和存的分开,特别是唤醒过程中,比如容器满了要唤醒取的线程,此时不能唤醒存的线程了,所以要分开。
public Condition mGetCondition;
public Condition mPutCondition;

public Object[] data = new Object[100];//固定容量是100

public int currentCount,getPosition,putPosition;

public BlockBuffer(){
mLock = new ReentrantLock(); 
mGetCondition = mLock.newCondition();
mPutCondition = mLock.newCondition();
}

/*存*/
public void put(Object obj){
mLock.lock();
try{
while(currentCount == data.length){
mPutCondition.await();
}
data[putPosition] = obj;
putPosition++;
if(putPosition ==  data.length){
putPosition = 0;
}
currentCount++;
mGetCondition.signal();
}catch(Exception e){
e.printStackTrace();
}finally{
mLock.unlock();
}
}

/*取*/
public Object get(){
mLock.lock();
try{
while(currentCount == 0){
mGetCondition.await();
}
Object object = data[getPosition];
getPosition++;
if(getPosition == data.length){
getPosition = 0;
}
currentCount--;
mPutCondition.signal();
return object;
}catch(Exception exception){
exception.printStackTrace();
}finally{
mLock.unlock();
}
return null;
} 
}
}



demo2:针对Condition的使用示列,A打印10次,B打印10次,C打印10,A打印10次,依次循环5次。

public class ConditionDemo2 {

public static void main(String[] args) {

final Outputer outputer = new Outputer();

new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0; i<10;i++){
outputer.outputA();
}
}
}).start();

new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0; i<10;i++){
outputer.outputB();
}
}
}).start();


new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0; i<10;i++){
outputer.outputC();
}
}
}).start();
}

public static class Outputer{

   public Lock mLock;
   
   public Condition mAConDition;
   public Condition mBConDition;
   public Condition mCConDition;
   
   public String currentFlag = "A";

public Outputer(){
mLock = new ReentrantLock();
mAConDition = mLock.newCondition();
mBConDition = mLock.newCondition();
mCConDition = mLock.newCondition();
}

public void outputA(){
mLock.lock();
try {
while(!currentFlag.equals("A")){
mAConDition.await();
}

for(int i=0;i<10;i++){
System.out.println("A_"+i);
}

currentFlag="B";
mBConDition.signal();
} catch (Exception e) {
e.printStackTrace();
}finally{
mLock.unlock();
}
}

        public void outputB(){
        mLock.lock();
try {
while(!currentFlag.equals("B")){
mBConDition.await();
}
for(int i=0;i<10;i++){
System.out.println("B_"+i);
}
currentFlag="C";
mCConDition.signal();

} catch (Exception e) {
e.printStackTrace();
}finally{
mLock.unlock();
}
}


public void outputC(){
mLock.lock();
try {
while(!currentFlag.equals("C")){
mCConDition.await();
}
for(int i=0;i<10;i++){
System.out.println("C_"+i);
}
currentFlag="A";
mAConDition.signal();

} catch (Exception e) {
e.printStackTrace();
}finally{
mLock.unlock();
}
}

}

}


注意:codition与wait、notif区别在于,当所有的互斥方法中使用同一个condition对象是,其await() signal()功能是与wait()、notif()方法是相同的,当在互斥方法中如果是使用多个Condition对象,那么此时每一个对象的与另外的对象是独立的。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值