Synchronized与ReentrantLock的区别

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Synchronized与ReentrantLock的区别相关的知识,希望对你有一定的参考价值。

Java在编写多线程程序时,为了保证线程安全,需要对数据同步,经常用到两种同步方式就是Synchronized和重入锁ReentrantLock。

相同点和区别

相同点:这两种同步方式有很多相似之处,它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的(操作系统需要在用户态与内核态之间来回切换,代价很高,不过可以通过对锁优化进行改善)。

区别:这两种方式最大区别就是对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现。而ReentrantLock它是JDK1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成。

synchronized是托管给JVM执行的,而lock是java写的控制锁的代码。在Java1.5中,synchronize是性能低效的。因为这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用Java提供的Lock对象,性能更高一些。但是到了Java1.6,发生了变化。synchronize在语义上很清晰,可以进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在Java1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在未来的版本中还有优化余地。

synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。

而Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是CAS操作(CompareandSwap)。我们可以进一步研究ReentrantLock的源代码,会发现其中比较重要的获得锁的一个方法是compareAndSetState。这里其实就是调用的CPU提供的特殊指令。

Synchronized

Synchronized进过编译,会在同步块的前后分别形成monitorenter和monitorexit这个两个字节码指令。在执行monitorenter指令时,首先要尝试获取对象锁。如果这个对象没被锁定,或者当前线程已经拥有了那个对象锁,把锁的计算器加1,相应的,在执行monitorexit指令时会将锁计算器就减1,当计算器为0时,锁就被释放了。如果获取对象锁失败,那当前线程就要阻塞,直到对象锁被另一个线程释放为止。

public class SynDemo{  
    public static void main(String[] arg){  
        Runnable t1=new MyThread();  
        new Thread(t1,"t1").start();  
        new Thread(t1,"t2").start();  
    }  
}  
class MyThread implements Runnable {  
    @Override  
    public void run() {  
        synchronized (this) {  
            for(int i=0;i<10;i++)  
                System.out.println(Thread.currentThread().getName()+":"+i);  
        }  
    }  
}  

查看字节码指令:

技术分享

ReentrantLock

由于ReentrantLock是java.util.concurrent包下提供的一套互斥锁,相比Synchronized,ReentrantLock类提供了一些高级功能,主要有以下3项:

  • 等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。
  • 公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁,ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。
  • 锁绑定多个条件,一个ReentrantLock对象可以同时绑定对个对象。

ReentrantLock的用法如下:

public class SynDemo{  
    public static void main(String[] arg){  
        Runnable t1=new MyThread();  
        new Thread(t1,"t1").start();  
        new Thread(t1,"t2").start();  
    }  
}  
class MyThread implements Runnable {  
    private Lock lock=new ReentrantLock();  
    public void run() {  
            lock.lock();  
            try{  
                for(int i=0;i<5;i++)  
                    System.out.println(Thread.currentThread().getName()+":"+i);  
            }finally{  
                lock.unlock();  
            }  
    }  
}  

 

以上是关于Synchronized与ReentrantLock的区别的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程_ReentrantLock

Java 重入锁 ReentrantLock 原理分析

《Java并发编程的艺术》读后笔记-第五章 Java中的锁

ThreadLocal使用和原理简析

一线互联网常见的14个Java面试题,你颤抖了吗程序员?

Java并发编程15ReentrantLock实现原理深入探究