并发编程(十):Lock和Condition

JDK并发包通过Lock和Condition是来实现管程,既然synchronized也是管程模型的体现,那么为什么还要有lock和condition呢?两者又有何种区别呢?

1.Lock

1.1.Lock和synchronized的区别

1.1.1.性能上的区别

死锁问题中,对于死锁条件之一不可抢占条件(其他线程无法抢占线程已占用的资源),synchronized是无法处理的,因为其申请资源时,如果申请不到就直接进入了阻塞状态,而线程一旦进入阻塞状态,除非该线程主动调用notify方法,不然其他线程啥都干不了

因此,对于“不可抢占条件”,我们期望的是:占用资源的线程进一步申请其他资源时,如果申请不到,可以主动释放已占用的资源,这样不可抢占条件就被破坏了

因此我们可以重新设计一把锁(Lock)去解决上述问题:

  1. 能够响应中断:如果阻塞状态的线程能够响应中断信号,也就是说当我们给阻塞的线程发送中断信号时,能够唤醒他,那他就有机会释放已申请的资源
  2. 支持超时:线程申请资源一段时间后,不进入阻塞状态而是返回异常,也能释放已申请的资源
  3. 非阻塞式的获取锁:获取锁失败后,就直接返回,也能解决

Lock.class在上述三种解决方案如下:

// 支持中断的 API
void lockInterruptibly() 
  throws InterruptedException;
// 支持超时的 API
boolean tryLock(long time, TimeUnit unit) 
  throws InterruptedException;
// 支持非阻塞获取锁的 API
boolean tryLock();

1.1.2.可见性规则

Lock能够保证可见性

1.1.3.可重入锁

可重入锁:同一线程可以重复获取同一把锁,称为可重入锁

 private final Lock rtl = new ReentrantLock();//这个锁就是可重入锁

1.1.4.公平锁与非公平锁

ReentrantLock 有两个构造函数,一个是无参,一个是传入fair参数

fair参数代表的是公平策略,如果传入true表示需要构造一个公平锁,反之则代表要构造一个非公平锁

// 无参构造函数:默认非公平锁
public ReentrantLock() {
    sync = new NonfairSync();
}
// 根据公平策略参数创建锁
public ReentrantLock(boolean fair){
    sync = fair ? new FairSync() 
                : new NonfairSync();
}

公平锁:入口等待队列中谁等待时间长,就唤醒哪个线程(synchroized只支持公平锁)

非公平锁:不提供这个公平保证,有可能等待时间短的线程反而先被唤醒

1.2.锁的最佳实践

三个用锁的最佳实践:

  1. 永远只在更新对象的成员变量的时候加锁
  2. 永远只在访问可变的成员变量的时候加锁
  3. 永远不再调用其他方法时加锁:因为你也不知道其他方法内部封装的细节,可能方法内执行的是耗时的IO操作,也可能内部加锁了,此时双重加锁则可能导致死锁

此外还可以考虑:

  1. 减少锁的粒度
  2. 减少锁的占用时长

2.Condition

Condition实现了管程模型里面的条件变量

支持多个条件变量能让我们的并发程序可读性更好,实现也更容易。例如阻塞队列使用了两个条件变量

Lock和Condition不过是管程模型的一种实现。

2.1.同步和异步

同步:调用方需要等待结果,称为同步

异步:调用方不需要等待结果,就称为异步

异步的实现:

  1. 异步调用:调用方创建一个子线程去执行方法调用
  2. 异步方法:调用的方法内部创建一个子线程去执行逻辑并直接return

2.2.Dubbo中同步转异步

RPC调用的TCP层面中,线程是不会等待响应结果的,那么为什么在我们的实际使用中的RPC调用又大部分都是同步,那么这种异步转同步又是怎么实现的呢?

异步转同步,简单理解为,本来是异步的流程,我们强行变成了同步实现,如何实现变为同步呢?那肯定就需要让调用线程变为BLOCKED或者WAIT状态,变为这几个状态的方式就包括wait()等,因此我们再调用的方法的异步实现的代码块中需要加上Condition的逻辑,拿dubbo的异步转同步举例,就是说代码中发起RPC请求的逻辑中需要判断请求的响应是否收到这一条件,如果没有满足则调用wait等待,在满足条件时再去通知,这样,RPC调用的TCP层面中,线程是不会等待响应结果的异步流程变为了需要等待结果的同步流程,这也是我们之前所讲述的等待-通知的思想

// 创建锁与条件变量
private final Lock lock 
    = new ReentrantLock();
private final Condition done 
    = lock.newCondition();
 
// 调用方通过该方法等待结果
Object get(int timeout){
  long start = System.nanoTime();
  lock.lock();
  try {
	while (!isDone()) {
	  done.await(timeout);
      long cur=System.nanoTime();
	  if (isDone() || 
          cur-start > timeout){
	    break;
	  }
	}
  } finally {
	lock.unlock();
  }
  if (!isDone()) {
	throw new TimeoutException();
  }
  return returnFromResponse();
}
// RPC 结果是否已经返回
boolean isDone() {
  return response != null;
}
// RPC 结果返回时调用该方法   
private void doReceived(Response res) {
  lock.lock();
  try {
    response = res;
    if (done != null) {
      done.signal();
    }
  } finally {
    lock.unlock();
  }
}

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×