synchronized ReentrantLock 比较分析

  

  在编写多线程代码的时候,对于不容许并发的代码,不少须要加锁进行处理。在进行加锁处理时候,synchronized做为java的内置锁,同时也是java关键字,最为被人熟知,即便是最初级的java程序员,只要知道java并发处理的,都会知道syschronized。java

  java5.0以后,java提供了另一种加锁机制,ReentrantLock提供了更多更灵活的功能,在不少复杂场景下,ReentrantLock相比synchronized会更合适。程序员

  synchronized和ReentrantLock也常常会拿出来比较,这也是在面试中,面试官也常常会问到的一个问题。这里就简单整理一下synchronized与ReentrantLock的异同。 面试

相同点

  比较synchronized 和 ReentrantLock的相同点,毋庸置疑,它们的功能都是做为锁对访问进行控制。防止并发形成逻辑错误。算法

  一、在获取ReentrantLock的时候,有着与synchronized相同的互斥性(互斥性:当一个线程已经获取到某个锁的时候,其余线程没法获取到该锁)编程

  二、ReentrantLock 和 syschronized提供的相同的内存可见性(可见性:当一个线程修改一个变量的时候,其余的线程可以及时知道)安全

  三、虽然ReentrantLock命名为可重入锁,可是实际上synchronized也是同样可重入的(可重入:当一个线程拿到某个锁后,当该线程在释放该锁以前,能够重复获取该锁)多线程

 

  打个比方:好比你去食堂排队打饭,饭桶只有一个饭勺。你手中拿着勺子,其余人要打饭就得等你打完,这就叫互斥性(独占性),勺子就是。你一开始左手拿着勺子,以为姿式不爽,换成右手来拿勺子,此时勺子在你手里,你能够重复左手右手一个慢动做,这就叫可重入。当你打完饭的时候,饭桶里的饭明显少了不少(真的很能吃),其余来打饭的人立刻就能看到了,这就叫可见性,饭桶是主存,你的饭盆就是工做内存并发

  

不一样点

  写法

  首先最直观的不一样点,就是写法上的不一样。性能

1         Object object = new Object();
2         synchronized (object) {
3             doSomeThing(s);
4         }

 

1         Lock lock=new ReentrantLock();
2         lock.lock();
3         try {
4             doSomeThing(s);
5         }finally {
6             lock.unlock();
7         }

  synchronized的写法相比较更简单,使用synchronized会自动加锁,synchronized同步代码块退出后,会自动释放锁。测试

  ReentrantLock须要在fiinally手动释放锁,若是忘记释放锁,会形成很大的麻烦。

  

  synchronized必须写在同一个代码块中,没法进行拆分。ReentrantLock在能够在不一样的地方进行lock,unLock,代码也能够进行灵活编写。好比concurrentHashMap中,Segment继承ReentrantLock来进行锁操做。

  功能

  synchronized提供的功能相对单一,当须要对锁进行复杂操做的时候,synchronized就会显得力不从心。好比轮询锁、定时锁、可中断锁。

    轮询锁

  第一种场景,假设有一段代码同时须要获取两个锁,当咱们使用synchronized的时候,会这么写

1         Object object1 = new Object();
2         Object object2 = new Object();
3         synchronized (object1) {
4             synchronized (object2){
5                 doSomeThing(object1,object2);
6             }
7         }

  若是如今有另一段代码,仍然须要同时锁住object1和object2。synchronized就必须保证同步的顺序,若是先锁住object二、再锁住object1,就有可能产生死锁。当一段程序用到多个锁,容易搞乱顺序。特别在团队开发的时候,成员之间不会知道他人经过什么样的顺序进行加锁。

  

  这个时候ReentrantLock更加灵活的优势就体现出来了。ReentrantLock提供了tryLock()方法,咱们能够用这个方法来实现轮询。

 1         Lock lock1 = new ReentrantLock();
 2         Lock lock2 = new ReentrantLock();
 3         while (true) {
 4             if (lock1.tryLock()) {
 5                 try {
 6                     if (lock2.tryLock()) {
 7                         try {
 8                             doSomeThing(s);
 9                         } finally {
10                             lock2.unlock();
11                         }
12                     }
13                 } finally {
14                     lock1.unlock();
15                 }
16             }
17             SECONDS.sleep(1);
18         }

  使用ReentrantLock的tryLock(),如上述代码所示,当咱们尝试获取lock1成功、获取lock2失败的时候,咱们会释放lock1,休眠一秒后从新获取两个锁。这样的好处经过放弃已经获取到的锁避免了死锁的出现,代码的安全性更高。并且获取锁的过程当中,咱们能够灵活的插入一些代码,好比17行所示的,每当两个锁没有同时获取成功,咱们就进行一秒钟休眠(虽然正常项目中下咱们不会这么处理)。

  

    定时锁

  第二种场景,若是咱们须要一个程序在特定时间内完成,若是特定时间内没有拿到锁,就直接返回,放弃该任务。synchronized面对这种场景一样为难,又到了ReentrantLock登场时间。reentrantLock的tryLock(long timeout, TimeUnit unit)方法提供了超时返回的功能。以下示例代码:

1         Lock lock = new ReentrantLock();
2         if(!lock.tryLock(1,SECONDS)){
3             return;
4         }
5         try {
6             doSomeThing();
7         }finally {
8             lock.unlock();
9         }

  上例中,ReentrantLock尝试使用1秒的时候去获取锁,若是超过期间仍然没有获取到锁,则返回失败。若是获取锁成功,则继续执行目的代码。

    可中断锁

  在使用synchronized进行获取锁排队的时候,是没法响应中断的,当业务场景必须实时响应线程中断时,synchronized就不那么合适了。使用ReantrantLock可使用lockInterruptibly()方法。该方法支持线程在获取锁的过程当中,对线程进行中断。

1         Lock lock = new ReentrantLock();
2         lock.lockInterruptibly();
3         try {
4             doSomeThing(s);
5         } finally {
6             lock.unlock();
7         }

  性能

  在java 6.0以前,synchronized的性能方便比ReentrantLock弱一截,java 发布6.0以后,从新优化了synchronized的算法。ReentrantLock的性能仅比synchronized的性能稍微强一些。至于如今,在不一样系统,不一样硬件下,测试出来的性能都会有误差。这边我也懒得本身再去作实验尝试,因此就很少逼逼了。

  

  公平性

  当多个线程前后请求一个被占用的锁的时候,当该锁释放,若是有算法保证最先排队的线程最早拿到锁,则这个锁就是公平锁。

  synchronized是不公平,reentrantLock则同时提供了公平锁和不公平锁两种。公平锁的好处在于,线程的前后顺序,等待最久的线程先执行。然而公平锁的性能却不如非公平锁,缘由在于,假设线程A持有一个锁,而且B线程请求这个锁。因为这个锁已经被A线程持有,所以B将被挂起。当A释放的锁的时候,B将被唤醒,而后从新尝试获取该锁。与此同时,若是C线程也同时请求该锁,那么C可能在B被彻底唤醒以前获取该锁、并执行任务、释放锁。即线程C可能在A、B线程持有锁的间隔中,完成操做。B线程彻底唤醒以后会发现锁已经被释放了。所以并不影响B线程的执行。经过这种插队行为,能提升这个程序的吞吐量。

  默认建立的ReentrantLock都是非公平的锁,若是想建立一个公平锁,能够利用Reentrant的构造器。

  //ReentrantLock 构造器
  public ReentrantLock( boolean fair){ sync = fair ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync(); }   
Lock lock
= new ReentrantLock(true);

 

 

 

  如下简单贴一下reentranLock的源码,看下公平锁和非公平锁的区别:

 1     static final class FairSync extends ReentrantLock.Sync {
 2         3        
 4         //公平锁,直接进行队列
 5         final void lock() {
 6             acquire(1);
 7         }
 8     }
 9 
10 
11     static final class NonfairSync extends ReentrantLock.Sync {
12         13 
14         /**
15          * Performs lock.  Try immediate barge, backing up to normal
16          * acquire on failure.
17          */
18         //非公平锁,能够进行插队
19         final void lock() {
20             if (compareAndSetState(0, 1))
21                 setExclusiveOwnerThread(Thread.currentThread());
22             else
23                 acquire(1);
24         }
25 
26         protected final boolean tryAcquire(int acquires) {
27             return nonfairTryAcquire(acquires);
28         }
29     }

 

  java语言规范并无要求JVM以公平的方式来实现内置锁,各类JVM也确实没有这么作。ReentrantLock并无进一步下降锁的公平性,在等待线程队列中,依旧遵循先进先出的原则。

 

 

总结

 

   synchronized与ReentrantLock相比较以后,感受ReeantrantLock比synchronized有更强大的功能,更灵活的操做。可是相比起来,syschronzied使用门槛更低,简单粗暴,并且不会出现忘记解锁的情况。《Java并发编程实战》中建议,在synchronized没法知足需求的时候,再用ReeantrantLock,缘由大体以下:

  一、synchronized简单易用,容易上手,并且不容易出现忘记解锁的状况

  二、将来更可能提升synchronized的性能,由于synchronized是JVM的内置属性

相关文章
相关标签/搜索