Lock与Synchronized区别

2年前 (2022) 程序员胖胖胖虎阿
188 0 0

先说结论,后面详解

  1. synchronized是关键字,Lock是接口;

  2. synchronized是隐式的加锁,lock是显式的加锁;

  3. synchronized可以作用于方法上,lock只能作用于方法块;

  4. synchronized底层采用的是objectMonitor,lock采用的AQS;

  5. synchronized是阻塞式加锁,lock是非阻塞式加锁支持可中断式加锁,支持超时时间的加锁;

  6. synchronized在进行加锁解锁时,只有一个同步队列和一个等待队列, lock有一个同步队列,可以有多个等待队列;

  7. synchronized只支持非公平锁,lock支持非公平锁和公平锁;

  8. synchronized使用了object类的wait和notify进行等待和唤醒, lock使用了condition接口进行等待和唤醒(await和signal);

  9. lock支持个性化定制, 使用了模板方法模式,可以自行实现lock方法;

synchronized是关键字,Lock是接口

// lock接口的方法
void lock();
void unlock();
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void lockInterruptibly() throws InterruptedException;
Condition newCondition();

synchronized是隐式的加锁,lock是显式的加锁

// synchronized 作用于方法上时,是看不到有锁的释放的
// lock 作用于代码块, 经常是 try lock finally unlock 一起使用, 是有主动去加锁和解锁的

synchronized可以作用于方法上,lock只能作用于方法块

// synchronized作用于静态方法和普通方法的区别是?
1.作用于静态方法时,锁的对象是class
2.作用于普通方法是,锁的可以是任意的对象  
3.作用于代码块时,反编译后,monitorenter  monitorexit  exception时的monitorexit
4.作用于方法时,反编译后,方法的flag里面加一个acc_synchronized访问标志    
// lock 作用于代码块, 经常是 try lock finally unlock 一起使用    

synchronized是阻塞式加锁,lock是非阻塞式加锁支持可中断式加锁,支持超时时间的加锁

// lock更加灵活,提供了多种方式的加锁方法
tryLock(long time, TimeUnit unit) // 带超时时间的加锁方法
lockInterruptibly // 可中断加锁方法

synchronized底层采用的是objectMonitor,lock采用的AQS

// objectMonitor
A,B线程竞争获取锁时,加入B线程未获取到锁,会进入到objectMonitor的entrylist(同步队列),获取到锁的线程调用wait后会进入waitset(等待队列)
// lock的AQS
通过一个int类型的state变量,来判断锁是否被获取,未获取到锁的线程会通过cas加入到双端队列的尾部    

synchronized在进行加锁解锁时,只有一个同步队列和一个等待队列, lock有一个同步队列,可以有多个等待队列

// lock中可以有多个等待队列 condition

synchronized只支持非公平锁,lock支持非公平锁和公平锁

// lock 支持非公平锁和公平锁, 默认创建是非公平锁
公平锁与非公平锁的区别就在于获取锁的方式不同,公平锁获取,当前线程必须检查sync queue里面是否已经有排队线程。而非公平锁则不用考虑这一点,当前线程可以直接去获取锁。
// 非公平锁可能出现线程饥饿问题

synchronized使用了object类的wait和notify进行等待和唤醒, lock使用了condition接口进行等待和唤醒(await和signal)

// condition 用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll(),传统线程的通信方式,Condition都可以实现,这里注意,Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用newCondition()方法。
    
// synchronized wait()只有一个阻塞队列,notifyAll会唤起所有阻塞队列下的线程,而使用lock/condition,可以实现多个阻塞队列,signalAll只会唤起某个阻塞队列下的阻塞线程。

class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length)
         notFull.await();
       items[putptr] = x;
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signalAll();
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0)
         notEmpty.await();
       Object x = items[takeptr];
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signalAll();
       return x;
     } finally {
       lock.unlock();
     }
   }
 }
/* 上面代码中线程间共享的资源是一个Oject数组items,因为需要对其进行并发操作,所以显然需要制定同步策略。
  对容器的基本操作无非就是增删改查,但是显然我们不能只是简单的增,简单的删,因为我们得考虑边界情况,数组有长度,增只有在容器还有空间的情况下才能增;删除则只有在容器有元素的情况下才能减。于是在put方法中我们得先判断一下是否还有位置才能对容器进行add操作;在take方法中我们得先判断是否有元素才能进行remove操作,而且这个判断逻辑是无法避免的。执行判断逻辑的时候我们已经获得了锁(能不能在实际add或者remove的时候再加锁,显然不能,因为判断逻辑的实现依赖共享资源items,所以必须在判断逻辑之前已经获得锁),如果判断逻辑为false我们只能先释放已经获得的锁并且等待条件满足再继续获取锁执行(这也是Condition中await方法的作用)。
 */     

// 为什么需要使用Condition(什么时候需要使用await/signalAll/signal方法)?
   /*  
   因为有时候获得锁的线程发现其某个条件不满足导致不能继续后面的业务逻辑,此时该线程只能先释放锁,等待条件满足。那可不可以不释放锁的等待呢?比如将await方法替换为sleep方法(这也是面试经常问的await和sleep的区别)?
显然不行,因为等待的条件显然和共享的资源是有关的,在这个例子里,take方法会等待notEmpty条件,notEmpty指的是items不为空,意味着此时items是空的,那么就只有对items执行add操作,即其它线程调用put方法才有机会达到notEmpty的条件,所以如果使用sleep(不释放锁)来等待而不是await(释放锁)来等待,则会导致notEmpty这个条件永远满足不了。
    总结起来,就是获得锁的线程发现某个条件不满足而不能继续执行,而且该条件需要其它线程对共享资源进行操作才能触发,所以必须释放锁。
*/      

版权声明:程序员胖胖胖虎阿 发表于 2022年10月24日 下午5:48。
转载请注明:Lock与Synchronized区别 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...