Sunday, July 5, 2015

Difference between synchronized vs ReentrantLock?



Related: http://massivetechinterview.blogspot.com/2015/08/implement-lock-and-reentrantlock-in.html
http://liangfei.me/2015/11/09/java-synchronize-static-method/
两者的实现方式是不一样的,jvm 规范中写道,编译后的 synchronized method 会有一个 ACC_SYNCHRONIZED 的 flag,也就是说当 jvm 的方法调用指令(the method invocation instruction)从 the run-time constant pool 中查找到这个 method 的时候,已经知道它是一个synchronized method,所以锁操作是由方法调用以及返回指令来控制的。
而 synchronized block 的锁是由 monitorenter 和 monitorexit 这两个指令来控制。
JVM 拿到编译器编译好的 class 文件后,首先会把文件载入到内存中,class 文件当然会有自己的格式,所以需要由 ClassLoader 来解析文件的内容,这个解析出来的内容会用一个 Class 类的实例 - Class object 来表示,这个 object 可以通过 Java 的 ClassName.class来获取。
也就是说,Class object 是一个 Class 类型的实例(instance),而对象是一个 ClassName 的 instance。Class 和 ClassName都是类型,ClassName是由 class 关键字定义的,而Class是内置类型。
因此成员方法的synchronized method 就等价于 synchronized (this) block,即下面两种方式是等价的。
public synchronized void fun1() {
    // do something here
}
public synchronized void fun2() {
    synchronized (this) {
    // do something here
    }
}
成员方法是属于 this,而静态方法是属于 Class Object,那么静态方法的 synchronized method 也就等价于下面这种形式的 synchronized block 了。
public static synchronized void fun2() {
    synchronized (ClassName.class) {
    // do something here
    }
}

并发编程锁之ReentrantLock总结
悲观锁就如其名字一样:悲观锁认为并发访问一定会导致状态不一致问题,所以在并发操作前一定要锁住资源,让并发线程一个接一个串行化去访问。而乐观锁就不一样了,乐观锁认为并发访问在大多数情况下是不会导致状态不一致问题,所以可以放心的去访问,一旦出现问题再说,本质上乐观锁是不会对共享资源添加锁限制的

悲观锁在Java中就是我们所熟知的锁,实现方式主要分为两种:synchronized和Lock,而乐观锁的实现主要通过CAS操作实现

​ a.在高版本JDK中,已经对synchronized进行了优化,synchronized和Lock方式在性能方面差别已不太明显
​ b.synchronized最致命的缺陷是:synchronized不支持中断和超时,也就是说通过synchronized一旦被阻塞住,如果一直无法获取到所资源就会一直被阻塞,即使中断也没用,这对并发系统的性能影响太大了;Lock支持中断和超时、还支持尝试机制获取锁,对synchronized进行了很好的扩展,所以从灵活性上Lock是明显优于synchronized的
并发编程锁之synchronized总结
Lock主要是通过编码的方式实现锁,其核心就是:CAS+循环,CAS原子操作需要依赖底层硬件层特殊的CPU指令

synchronized主要通过底层JVM进行实现,而且JVM为了优化,产生偏向锁、轻量级锁、重量级锁

synchronized实现同步主要分为两种情况:
​ 1、同步方法:synchronized方法则会被翻译成普通的方法调用,在JVM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位设置成1,表示该方法是同步方法,当某个线程要访问某个方法的时候,使用调用该方法的对象(普通方法同步)或该方法所属的Class在JVM的内部对象表示Klass做为监视器锁(静态方法同步,全局锁),这时如果其他线程来请求执行方法,会因为无法获得监视器锁而被阻断住。值得注意的是,如果在方法执行过程中,发生了异常,并且方法内部并没有处理该异常,那么在异常被抛到方法外面之前监视器锁会被自动释放
​ 2、同步代码块:对于同步代码块,JVM采用monitorenter、monitorexit两个指令来实现同步。monitorenter指令插入到同步代码块的开始位置,monitorexit指令插入到同步代码块的结束位置,JVM需要保证每一个monitorenter都有一个monitorexit与之相对应,这样就保证了执行monitorexit指令的线程是monitor监视器的所有者。根据虚拟机规范的要求,在执行monitorenter指令时,首先要尝试获取栈顶对象的锁,如果这个对象没有被锁定,或者当前线程已经拥有了那个对象的锁,把锁的计数器加1,相应地,在执行monitorexit指令时会将栈顶对象锁计数器减1,当计数器为0时,锁就会被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到对象锁被另外一个线程释放为止。
Java中对象的内存布局主要分为三个区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。synchronized的实现方式依赖于对象头,所以,这里我们先来简单介绍下Java中对象头。
1517815118540
实例数据区主要是实例属性数据存储区域,对齐填充在HotSpot中主要采用8字节对齐方式,对象头和实例数据区字节数不是8的倍数,采用对齐填充方式让其等于8的倍数。这里来看下对象头,如果是数组类型,其由MarkWord、length(数组长度)和Pointer,Pointer是指向该对象的元数据信息,即该对象的Class实例,对象的方法定义都是在Class实例中;如果是非数组类型,对象头只包含:MarkWord和Pointer两部分。

锁分类

早期,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统互斥Mutex Lock来实现的,而操作系统实现线程之间的阻塞、调度、唤醒等操作时需要从用户态切换到内核态,最后再由内核态切换到用户态,将CPU的控制权交由用户进程,用户态与内核态之间频繁的切换,严重影响锁的性能,这也是为什么早期的synchronized效率低的原因。
在Java 6之后Java官方对从JVM层面对synchronized进行较大优化,所以现在的synchronized锁效率也优化得很不错了,为了减少获得锁和释放锁所带来的性能消耗,引入了偏向锁、轻量级锁和自旋锁等概念,下面就来分析下它们的原理。
轻量级锁
轻量级锁实现的背后基于这样一种场景假设:在真实生产环境下,我们程序中的大部分同步代码一般都处于无锁竞争状态,轻量级锁主要解决如下场景:线程A和线程B都要访问对象o的同步方法,但是它们之间不会同时访问,线程A访问完成后线程B再去访问,它们之间访问类似于交替访问,因此,这种情况下并不会产生锁竞争问题。在无锁竞争的情况下完全可以避免调用操作系统层面的重量级互斥锁,只需要依靠CAS原子指令就可以完成锁的获取及释放,但是当检测到存在锁竞争的情况下,轻量级锁就会膨胀为重量级锁。
下面通过如下同步代码块分析下轻量级锁实现的大致流程:


1
2
3
4
public class Obj {
  public synchronized void fun1(){
  }
}

​ 1、当代码进入同步块时,即调用Obj.fun1()方法,当Obj实例为无锁状态,即对象头的锁标志位为01,当前线程会在栈帧中创建一个锁记录(Lock Record),同时将锁对象Obj的对象头中MarkWord拷贝到锁记录中,因为栈是线程私有的,Java方法的调用就是通过栈帧得到入栈和出栈实现的,所以将锁记录保存到栈帧中,这一步主要完成MarkWord拷贝过程;
​ 3、更新失败情况主要如下:比如有两个线程A和线程B同时竞争锁,执行步骤1时由于当前对象处于无锁状态,所以这两个线程都会在它们的栈帧中创建Lock Record,然后将对象头中的MarkWord拷贝进去,然后它们都同时进入步骤2执行CAS原子操作将对象头中的锁指针指向自己栈帧中的Lock Record,所以,肯定有一个成功,另一个就会失败,成功的就是获取到偏向锁的线程,失败的就是没有获取偏向锁的线程。如果更新失败,JVM会先检查锁对象的MarkWord是否指向当前线程的锁记录,如果是则说明当前线程拥有锁对象的锁,可以直接进入同步块,这是重入锁特性,不是则说明其有其它线程抢占了锁
​ 4、其它线程抢占了锁,说明存在锁竞争情况,这时轻量级锁并不为立即膨胀为重量级锁,而是进入自旋模式,自旋模式期间还是无法获取锁,就会膨胀为重量级锁,大致思路:尽量降低阻塞的可能性,这对那些执行时间很短的代码块来说有非常重要的性能提高。
为什么要进入自旋模式原因?
膨胀为重量级锁会涉及到有用户态切换到内核态进行线程的休眠和唤醒操作,然后再切换到用户态,这些操作给系统的并发性能带来了很大的压力,共享数据的锁定状态可能只会持续很短的一段时间,为了这段时间去挂起和恢复线程并不值得,可以让后面请求锁的那个线程”稍等一下”,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,只需要让线程执行一个忙循环(自旋),所以自旋会对CPU造成资源浪费,特别是长时间无法获取锁的情况下,所以自旋次数一定要设置成一个合理的值,而不能无限自旋下去。JDK1.6默认是开启了自旋锁功能,而且对自旋次数也不在是固定值,而是通过一套优化机制进行自适应,简化了对自旋锁的使用。

Difference between synchronized vs ReentrantLock?
http://java67.blogspot.com/2012/08/5-thread-interview-questions-answers-in.html
ReentrantLock mostly uses atomic variable and faster CAS operation to provides better performance. 
Key points to mention is difference between ReentrantLock and synchronized keyword in Java, which includes ability to acquire lock interruptibly, timeout feature while waiting for lock etc. ReentrantLock also gives option to create fair lock in Java.

Two key feature of ReentrantLock, which provides more control on lock acquisition is trying to get a lock with ability to interrupt, and a timeout on waiting for lock.

As per Javadoc, ReentrantLock is mutual exclusive lock, similar to implicit locking provided bysynchronized keyword in Java, with extended feature like fairness, which can be used to provide lock to longest waiting thread. 

Difference between ReentrantLock and synchronized keyword in Java
main difference between synchronized and ReentrantLock is ability to trying for lock interruptibly, and with timeout. Thread doesn’t need to block infinitely, which was the case with synchronized. Let’s see few more differences between synchronized and Lock in Java.

1) Another significant difference between ReentrantLock and synchronized keyword is fairnesssynchronizedkeyword doesn't support fairness. Any thread can acquire lock once released, no preference can be specified, on the other hand you can make ReentrantLock fair by specifying fairness property, while creating instance of ReentrantLock. Fairness property provides lock to longest waiting thread, in case of contention.

2) Second difference between synchronized and Reentrant lock is tryLock() method. ReentrantLock provides convenient tryLock() method, which acquires lock only if its available or not held by any other thread. This reduceblocking of thread waiting for lock in Java application.

3) One more worth noting difference between ReentrantLock and synchronized keyword in Java is, ability to interruptThread while waiting for Lock. In case of synchronized keyword, a thread can be blocked waiting for lock, for an indefinite period of time and there was no way to control that. ReentrantLock provides a method called lockInterruptibly(), which can be used to interrupt thread when it is waiting for lock. Similarly tryLock() with timeout can be used to timeout if lock is not available in certain time period.

4) ReentrantLock also provides convenient method to get List of all threads waiting for lock.
Benefits of ReentrantLock in Java
1) Ability to lock interruptibly.
2) Ability to timeout while waiting for lock.
3) Power to create fair lock.
4) API to get list of waiting thread for lock.
5) Flexibility to try for lock without blocking.
Disadvantages of ReentrantLock in Java
Major drawback of using ReentrantLock in Java is wrapping method body insidtry-finally block, which makes code unreadable and hides business logic. 

Another disadvantage is that, now programmer is responsible for acquiring and releasing lock, which is a power but also opens gate for new subtle bugs, when programmer forget to release the lock in finally bloc

Code:
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();

    }
     * <p>Acquires the lock if it is not held by another thread and returns
     * immediately, setting the lock hold count to one.
     * <p>If the current thread already holds the lock then the hold
     * count is incremented by one and the method returns immediately.
    public void lock() {
        sync.lock();

    }
Acquires the lock unless the current thread is interrupted.
Acquires the lock if it is not held by another thread and returns immediately, setting the lock hold count to one.
If the current thread already holds this lock then the hold count is incremented by one and the method returns immediately.
If the lock is held by another thread then the current thread becomes disabled for thread scheduling purposes and lies dormant until one of two things happens:
  • The lock is acquired by the current thread; or
  • Some other thread interrupts the current thread.
If the lock is acquired by the current thread then the lock hold count is set to one.
If the current thread:
  • has its interrupted status set on entry to this method; or
  • is interrupted while acquiring the lock,

then InterruptedException is thrown and the current thread's interrupted status is cleared.
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);

    }

boolean java.util.concurrent.locks.ReentrantLock.tryLock(long timeout, TimeUnit unit) throws InterruptedException


If the lock is held by another thread then this method will return immediately with the value false.


Attempts to release this lock.

If the current thread is the holder of this lock then the hold count is decremented. If the hold count is now zero then the lock is released. If the current thread is not the holder of this lock then IllegalMonitorStateException is thrown.

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

    }
Please read full article from Difference between synchronized vs ReentrantLock?

Labels

Review (572) System Design (334) System Design - Review (198) Java (189) Coding (75) Interview-System Design (65) Interview (63) Book Notes (59) Coding - Review (59) to-do (45) Linux (43) Knowledge (39) Interview-Java (35) Knowledge - Review (32) Database (31) Design Patterns (31) Big Data (29) Product Architecture (28) MultiThread (27) Soft Skills (27) Concurrency (26) Cracking Code Interview (26) Miscs (25) Distributed (24) OOD Design (24) Google (23) Career (22) Interview - Review (21) Java - Code (21) Operating System (21) Interview Q&A (20) System Design - Practice (20) Tips (19) Algorithm (17) Company - Facebook (17) Security (17) How to Ace Interview (16) Brain Teaser (14) Linux - Shell (14) Redis (14) Testing (14) Tools (14) Code Quality (13) Search (13) Spark (13) Spring (13) Company - LinkedIn (12) How to (12) Interview-Database (12) Interview-Operating System (12) Solr (12) Architecture Principles (11) Resource (10) Amazon (9) Cache (9) Git (9) Interview - MultiThread (9) Scalability (9) Trouble Shooting (9) Web Dev (9) Architecture Model (8) Better Programmer (8) Cassandra (8) Company - Uber (8) Java67 (8) Math (8) OO Design principles (8) SOLID (8) Design (7) Interview Corner (7) JVM (7) Java Basics (7) Kafka (7) Mac (7) Machine Learning (7) NoSQL (7) C++ (6) Chrome (6) File System (6) Highscalability (6) How to Better (6) Network (6) Restful (6) CareerCup (5) Code Review (5) Hash (5) How to Interview (5) JDK Source Code (5) JavaScript (5) Leetcode (5) Must Known (5) Python (5)

Popular Posts