简介 ReentrantLock
和synchronized
都是提供了同步的功能,JDK1.6
之后对synchronized
性能进行了优化,所以两者的性能上几乎没什么区别,但是ReentrantLock
提供了了一些高级功能。
等待可中断:在synchronized
中,如果一个线程在等待锁,他只用两种结果,要么获得锁执行完,要么一直保持等待。可中断的等待是通知正在等待的线程,告诉他没必要再等待后。
实现公平锁:公平锁:会按照时间的先后顺序,保证先到先得。特点是它不会产生饥饿现象。而synchroized关键字进行所控制时,锁是非公平的。而重入锁可以设置为公平锁。 public ReetranLock(boolean fair)
当fair
为true
时,表示锁是公平的。实现公平锁必然要求系统维护一个有序队列,因此公平锁的成本比较高,性能也非常低向。默认情况下锁是非公平的。
绑定多个条件:类似于Object
类的wait
和notify
方法,它是与ReentrantLock
绑定的条件,可以绑定多个条件。
一个简单的例子 注意:退出临界区要释放锁,否则其他线程就没有机回访问临界区了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class TestReentrantLock implements Runnable { public static ReentrantLock rlock = new ReentrantLock (); public static int i=0 ; @Override public void run () { for (int j=0 ;j<1000000 ;j++){ rlock.lock(); try { i++; }finally { rlock.unlock(); } } } public static void main (String args[]) throws InterruptedException { TestReentrantLock tl = new TestReentrantLock (); Thread t1 = new Thread (tl); Thread t2 = new Thread (tl); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
注意:退出临界区要释放锁,否则其他线程就没有机回访问临界区了。
Lock接口 Lock
接口是JDK1.5
新加的同步工具接口,它的实现类有ReentrantLock
、WriteLock
等,接口中定义了通用的方法:
1 2 3 4 5 6 7 8 9 10 11 12 void lock () ;void unlock () ;void lockInterruptibly () throws InterruptedException;boolean tryLock () ;boolean tryLock (long time, TimeUnit unit) throws InterruptedException;Condition newCondition () ;
其中lock
和unlock
方法提供了synchronized
的功能,其他方法使得同步过程更加的灵活。
什么叫重入锁 一个线程可以多次进入,当然必须多次释放锁。
1 2 3 4 5 6 7 8 9 10 rlock.lock(); rlock.lock(); try { i++; }finally { rlock.unlock(); rlock.unlock(); }
下面根据案例主要介绍ReentrantLock
的用法,在后面的文章中介绍它的实现原理。
中断响应 如果一个线程正在等待锁,那么它可以收到一个通知,被告知无序再等待,可以停止工作了。在synchronized
中, 如果一个线程在等待锁,他只用两种结果,要么获得锁执行完,要么一直保持等待。
下面通过一个死锁的例子,介绍中断响应的过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 public class DeathLock implements Runnable { public static ReentrantLock lock1 = new ReentrantLock (); public static ReentrantLock lock2 = new ReentrantLock (); int lock; public DeathLock (int lock) { this .lock = lock; } @Override public void run () { try { if (lock==1 ){ lock1.lockInterruptibly(); System.out.println(Thread.currentThread().getName()+" get lock1" ); try { Thread.sleep(1000 ); }catch (Exception e){} lock2.lockInterruptibly(); System.out.println(Thread.currentThread().getName()+" get lock2" ); }else { lock2.lockInterruptibly(); System.out.println(Thread.currentThread().getName()+" get lock2" ); try { Thread.sleep(1000 ); }catch (InterruptedException e){} lock1.lockInterruptibly(); System.out.println(Thread.currentThread().getName()+" get lock1" ); } }catch (InterruptedException e){ e.printStackTrace(); }finally { if (lock1.isHeldByCurrentThread()) lock1.unlock(); if (lock2.isHeldByCurrentThread()) lock2.unlock(); System.out.println(Thread.currentThread().getName()+" 退出!" ); } } public static void main (String args[]) throws InterruptedException{ Thread thread1 = new Thread (new DeathLock (1 ),"thread1" ); Thread thread2 = new Thread (new DeathLock (1 ),"thread2" ); thread1.start(); thread2.start(); Thread.sleep(1000 ); thread2.interrupt(); } }
执行过程是thread1
占用lock1
,休眠500毫秒,然后想占用lock2
,与此同时,thread2
占用lock2
,休眠1000毫秒后在请求lock1
。可是当thread1
,想请求lock2
时,已经被thread2
占用,因此只能进入阻塞状态,thread2
也同理进入阻塞状态。因此进入死锁。但是这里使用了lockInterruptibly()
方法。这是一个可以对中断进行响应的锁申请动作,即在等待锁的过程中可以响应中断。在thred2
调用interrupt()
方法,thread2
线程被中断,thread2
放弃对lock
的申请,同时释放已获得的lock2
,所以thread1
可以得到lock2
继续执行下去。
结果为:
thread2
先中断,抛出异常,跳入finally
块,释放资源,最终退出。
锁申请等待限时 如果给定一个等待时间,超过时间,让线程自动放弃。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class TimeLock implements Runnable { public static ReentrantLock lock = new ReentrantLock (); @Override public void run () { try { if (lock.tryLock(2 , TimeUnit.SECONDS)){ Thread.sleep(5000 ); }else { System.out.println(Thread.currentThread().getName()+" get lock failed" ); } } catch (InterruptedException e) { e.printStackTrace(); }finally { if (lock.isHeldByCurrentThread()) lock.unlock(); } } public static void main (String args[]) { TimeLock t = new TimeLock (); Thread thread1 = new Thread (t,"thread1" ); Thread thread2 = new Thread (t,"thread2" ); thread1.start(); thread2.start(); } }
tryLock()
两个参数分别表示等待时长和计时单位,表示线程在请求锁的过程中,最多等待5秒,如果超过改时间则返回false,如果成果获得锁,则返回true
。 该程序中首先任意一个线程先获得锁,然后休眠5秒,然而它一直占有锁,因此另一个线程无法再2秒内获得锁,因此失败。
tryLock()
方法也可以不带参数,这种情况下,当前线程会尝试获得锁,如果锁未被其他线程占用,则申请锁会成功,把那个返回true
,如果锁被其他线程占用,则当前线程不会等待,而是立即返回false
。这种模式下不会引起线程等待,因此也不会产生死锁。
公平锁 大多数情况下,锁的申请都是非公平的,也就是说,线程1首先申请锁A,接着线程2也请求了锁A,当锁A可用时,线程1,2都有可能获得锁,系统只是在等待队列中随机挑选一个,因此不能保证公平性。 所以有了公平锁,公平锁:会按照时间的先后顺序,保证先到先得。特点是它不会产生饥饿现象。而synchroized
关键字进行所控制时,锁是非公平的。而重入锁可以设置为公平锁。public ReetranLock(boolean fair)
当fair
为true
时,表示锁是公平的。实现公平锁必然要求系统维护一个有序队列,因此公平锁的成本比较高,性能也非常低向。默认情况下锁是非公平的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class FairLock implements Runnable { public static ReentrantLock lock = new ReentrantLock (true ); @Override public void run () { while (true ) try { lock.lock(); System.out.println(Thread.currentThread().getName()+" get lock !" ); }finally { lock.unlock(); } } public static void main (String args[]) { FairLock f = new FairLock (); Thread thread1 = new Thread (f,"thread1" ); Thread thread2 = new Thread (f,"thread2" ); thread1.start(); thread2.start(); } }
部分结果为:
可以看出两个线程基本上是交替获得锁。
Condition条件(搭配重入锁使用) Condition
类似于wait()
和notify()
的功能,它是与重入锁关联使用的。Lock
接口中提供了newCondition()
方法,该方法可以返回绑定到此Lock
的Condition
实例。
方法解释:await
方法会使当前线程等待,同时释放当前锁,当其他线程中使用signal()
或signalAll()
方法时,线程会重新获得锁并继续执行,当线程被中断时,也能跳出等待。与Object
的wait()
方法相似。singal()
方法用于唤醒一个在等待中的线程。 注意:以上连个方法调用之前必须当前线程拥有锁。否则抛出IllegalMonitorStateException
异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class ConditionDemo implements Runnable { public static ReentrantLock lock = new ReentrantLock (true ); public static Condition condition = lock.newCondition(); @Override public void run () { try { lock.lock(); condition.await(); System.out.println(Thread.currentThread().getName()+" get lock !" ); } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } public static void main (String args[]) throws InterruptedException { ConditionDemo t = new ConditionDemo (); Thread thread1 = new Thread (t,"thread1" ); thread1.start(); Thread.sleep(2000 ); lock.lock(); System.out.println(Thread.currentThread().getName() + " get lock !" ); condition.signal(); lock.unlock(); } }
thread1
线程调用await
时,要求线程持有相关的重入锁,调用后,线程释放这把锁,同理signal
方法调用时,也要求线程先获得相关的锁,在signal
方法调用后,系统会从当前的Condition
对象的等待队列中唤醒一个线程,一旦线程唤醒,它会重新尝试获得之前绑定的锁,一旦成功获取await
方法返回,继续执行。在调用signal
后先睡眠2秒,并且保持了锁,释放了锁之后,await
方法获取锁后才得以返回继续执行。因此打印出来的时间差为2000毫秒。
最后 上面结合例子介绍了ReentrantLock
主要的用法,还有一些很有意思的用法,比如正在等待锁的线程,当前线程是否拥有锁等等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int getHoldCount () protected Thread getOwner () ; protected Collection<Thread> getQueuedThreads () ; int getQueueLength () ;protected Collection<Thread> getWaitingThreads (Condition condition) ; int getWaitQueueLength (Condition condition) ;boolean hasQueuedThread (Thread thread) ; boolean hasQueuedThreads () ;boolean hasWaiters (Condition condition) ; boolean isFair () boolean isHeldByCurrentThread () boolean isLocked ()
进入ReentrantLock
的源码发现,ReentrantLock
类的绝大部分功能是通过它的内部类Sync
来实现的,而Sync
又继承了AbstractQueuedSynchronizer
类。这就是大名鼎鼎的AQS
,Doug Lea
最著名的作品,后面的文章分析它的精华所在。
参考地址