在Java中通常实现锁有两种方式,一种是synchronized关键字,另一种是Lock。二者其实并没有什么必然联系,但是各有各的特点,在使用中可以进行取舍的使用。首先我们先对比下两者。
实现:
首先最大的不同:synchronized是基于JVM层面实现的,而Lock是基于JDK层面实现的。Lock却是基于JDK实现的,我们可以通过阅读JDK的源码来理解Lock的实现。
使用:
对于使用者的直观体验上Lock是比较复杂的,需要lock和realse,如果忘记释放锁就会产生死锁的问题,所以,通常需要在finally中进行锁的释放。但是synchronized的使用十分简单,只需要对自己的方法或者关注的同步对象或类使用synchronized关键字即可。但是对于锁的粒度控制比较粗,同时对于实现一些锁的状态的转移比较困难。
Lock的实现主要有ReentrantLock、ReadLock和WriteLock,简单分析一下ReentrantLock的实现和运行机制。
和lock经常搭配的Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set (wait-set)。
其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
在Condition中,用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll(),传统线程的通信方式,Condition都可以实现,这里注意,Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用newCondition()方法。
这样看来,Condition和传统的线程通信没什么区别,Condition的强大之处在于它可以为多个线程间建立不同的Condition(最后的例子体现该特性),下面引入常用方法API。
boolean isFair()
----Returns true if this lock has fairness set true.
----是否是公平锁
boolean isHeldByCurrentThread()
----Queries if this lock is held by the current thread.
----当前线程是否拥有这把锁
boolean isLocked()
----Queries if this lock is held by any thread.
----锁是否被锁定
void lock()
----Acquires the lock.
----获取锁
void lockInterruptibly()
----Acquires the lock unless the current thread is interrupted.
----立即响应中断请求的lock
Condition newCondition()
----Returns a Condition instance for use with this Lock instance.
----返回一个condition
boolean tryLock()
----Acquires the lock only if it is not held by another thread at the time of -----invocation.
----尝试获取锁,返回结果为boolean
boolean tryLock(long timeout, TimeUnit unit)
void unlock()
----Attempts to release this lock.
----释放锁
示例:
public class Reen {
ReentrantLock lock = new ReentrantLock();
Condition con1 = lock.newCondition();
Condition con2 = lock.newCondition();
List<String> list = new ArrayList<>();
void pro(){
while(true){
try {
lock.lock();
while(list.size()==10){
con2.signal();
con1.await();
}
list.add("a");
System.out.println(Thread.currentThread().getName()+" add:"+list.size());
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
void cus(){
while(true){
try {
lock.lock();
System.out.println(lock.getWaitQueueLength(con1));
while(list.size()==0){
con1.signal();
con2.await();
}
list.remove(0);
System.out.println(Thread.currentThread().getName()+" rem:"+list.size());
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws Exception {
Reen r = new Reen();
new Thread(r::pro).start();
new Thread(r::pro).start();
new Thread(r::pro).start();
new Thread(r::pro).start();
new Thread(r::pro).start();
new Thread(r::cus).start();
new Thread(r::cus).start();
new Thread(r::cus).start();
new Thread(r::cus).start();
new Thread(r::cus).start();
}
}
最后,补充提一下这两中机制的具体区别。据我所知,synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。
而Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是CAS操作(Compare and Swap)。我们可以进一步研究ReentrantLock的源代码,会发现其中比较重要的获得锁的一个方法是compareAndSetState。这里其实就是调用的CPU提供的特殊指令。
思考:为什么判断list长度用while而不用if?