synchronized lock和ReentrantLock的区别

关于synchronized和ReentrantLock之多线程同步详解 - 简书
关于synchronized和ReentrantLock之多线程同步详解
本篇文章总结关于多线程编程的一些知识点,这其中两个重要的部分就是对于synchronized和ReentrantLock的使用和介绍。
一、线程同步问题的产生及解决方案
问题的产生:Java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突。如下例:假设有一个卖票系统,一共有100张票,有4个窗口同时卖。
public class Ticket implements Runnable {
// 当前拥有的票数
private int num = 100;
public void run() {
while (true) {
if (num & 0) {
Thread.sleep(10);
} catch (InterruptedException e) {
// 输出卖票信息
System.out.println(Thread.currentThread().getName() + ".....sale...." + num--);
public static void main(String[] args) {
Ticket t = new Ticket();//创建一个线程任务对象。
//创建4个线程同时卖票
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
//启动线程
t1.start();
t2.start();
t3.start();
t4.start();
输出部分结果:
Thread-1.....sale....2Thread-0.....sale....3Thread-2.....sale....1Thread-0.....sale....0Thread-1.....sale....0Thread-3.....sale....1
显然上述结果是不合理的,对于同一张票进行了多次售出。这就是多线程情况下,出现了数据“脏读”情况。即多个线程访问余票num时,当一个线程获得余票的数量,要在此基础上进行-1的操作之前,其他线程可能已经卖出多张票,导致获得的num不是最新的,然后-1后更新的数据就会有误。这就需要线程同步的实现了。
问题的解决:因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。
一共有两种锁,来实现线程同步问题,分别是:synchronized和ReentrantLock。下面我们就带着上述问题,看看这两种锁是如何解决的。
二、synchronized关键字
1.synchronized简介
synchronized实现同步的基础:Java中每个对象都可以作为锁。当线程试图访问同步代码时,必须先获得对象锁,退出或抛出异常时必须释放锁。Synchronzied实现同步的表现形式分为:代码块同步和方法同步。
2.synchronized原理
JVM基于进入和退出Monitor对象来实现代码块同步和方法同步,两者实现细节不同。
代码块同步:在编译后通过将monitorenter指令插入到同步代码块的开始处,将monitorexit指令插入到方法结束处和异常处,通过反编译字节码可以观察到。任何一个对象都有一个monitor与之关联,线程执行monitorenter指令时,会尝试获取对象对应的monitor的所有权,即尝试获得对象的锁。
方法同步:synchronized方法在method_info结构有ACC_synchronized标记,线程执行时会识别该标记,获取对应的锁,实现方法同步。
两者虽然实现细节不同,但本质上都是对一个对象的监视器(monitor)的获取。任意一个对象都拥有自己的监视器,当同步代码块或同步方法时,执行方法的线程必须先获得该对象的监视器才能进入同步块或同步方法,没有获取到监视器的线程将会被阻塞,并进入同步队列,状态变为BLOCKED。当成功获取监视器的线程释放了锁后,会唤醒阻塞在同步队列的线程,使其重新尝试对监视器的获取。
对象、监视器、同步队列和执行线程间的关系如下图:
3.synchronized的使用场景
①方法同步
public synchronized void method1
锁住的是该对象,类的其中一个实例,当该对象(仅仅是这一个对象)在不同线程中执行这个同步方法时,线程之间会形成互斥。达到同步效果,但如果不同线程同时对该类的不同对象执行这个同步方法时,则线程之间不会形成互斥,因为他们拥有的是不同的锁。
②代码块同步
synchronized(this){ //TODO }
③方法同步
public synchronized static void method3
锁住的是该类,当所有该类的对象(多个对象)在不同线程中调用这个static同步方法时,线程之间会形成互斥,达到同步效果。
④代码块同步
synchronized(Test.class){ //TODO}
⑤代码块同步
synchronized(o) {}
这里面的o可以是一个任何Object对象或数组,并不一定是它本身对象或者类,谁拥有o这个锁,谁就能够操作该块程序代码。
4.解决线程同步的实例
针对上述方法,具体的解决方式如下:
public class Ticket implements Runnable {
// 当前拥有的票数
private int num = 100;
public void run() {
while (true) {
Thread.sleep(10);
} catch (InterruptedException e) {
synchronized (this) {
// 输出卖票信息
if(num&0){
System.out.println(Thread.currentThread().getName() + ".....sale...." + num--);
输出部分结果:
Thread-2.....sale....10Thread-1.....sale....9Thread-3.....sale....8Thread-0.....sale....7Thread-2.....sale....6Thread-1.....sale....5Thread-2.....sale....4Thread-1.....sale....3Thread-3.....sale....2Thread-0.....sale....1
可以看出实现了线程同步。同时改了一下逻辑,在进入到同步代码块时,先判断现在是否有没有票,然后再买票,防止出现没票还要售出的情况。通过同步代码块实现了线程同步,其他方法也一样可以实现该效果。
三、ReentrantLock锁
1.Lock接口
Lock,锁对象。在Java中锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源(但有的锁可以允许多个线程并发访问共享资源,比如读写锁,后面我们会分析)。在Lock接口出现之前,Java程序是靠synchronized关键字(后面分析)实现锁功能的,而JAVA SE5.0之后并发包中新增了Lock接口用来实现锁的功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁,缺点就是缺少像synchronized那样隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性,可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。
Lock接口的主要方法(还有两个方法比较复杂,暂不介绍):
void lock():
执行此方法时, 如果锁处于空闲状态, 当前线程将获取到锁. 相反, 如果锁已经被其他线程持有, 将禁用当前线程, 直到当前线程获取到锁.boolean tryLock():如果锁可用, 则获取锁, 并立即返回true, 否则返回false. 该方法和lock()的区别在于, tryLock()只是"试图"获取锁, 如果锁不可用, 不会导致当前线程被禁用, 当前线程仍然继续往下执行代码. 而lock()方法则是一定要获取到锁, 如果锁不可用, 就一直等待, 在未获得锁之前,当前线程并不继续向下执行. 通常采用如下的代码形式调用tryLock()方法:void unlock():执行此方法时, 当前线程将释放持有的锁. 锁只能由持有者释放, 如果线程并不持有锁, 却执行该方法, 可能导致异常的发生.Condition newCondition():条件对象,获取等待通知组件。该组件和当前的锁绑定,当前线程只有获取了锁,才能调用该组件的await()方法,而调用后,当前线程将缩放锁。
ReentrantLock,一个可重入的互斥锁,它具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。(重入锁后面介绍)
2.ReentrantLock的使用
关于ReentrantLock的使用很简单,只需要显示调用,获得同步锁,释放同步锁即可。
ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁
.....................
lock.lock(); //如果被其它资源锁定,会在此等待锁释放,达到暂停的效果
} finally {
lock.unlock();
3.解决线程同步的实例
针对上述方法,具体的解决方式如下:
public class Ticket implements Runnable {
// 当前拥有的票数
private int num = 100;
ReentrantLock lock = new ReentrantLock();
public void run() {
while (true) {
Thread.sleep(10);
} catch (InterruptedException e) {
lock.lock();
// 输出卖票信息
if (num & 0) {
System.out.println(Thread.currentThread().getName() + ".....sale...." + num--);
lock.unlock();
四、重入锁
当一个线程得到一个对象后,再次请求该对象锁时是可以再次得到该对象的锁的。具体概念就是:自己可以再次获取自己的内部锁。Java里面内置锁(synchronized)和Lock(ReentrantLock)都是可重入的。
public class SynchronizedTest {
public void method1() {
synchronized (SynchronizedTest.class) {
System.out.println("方法1获得ReentrantTest的锁运行了");
method2();
public void method2() {
synchronized (SynchronizedTest.class) {
System.out.println("方法1里面调用的方法2重入锁,也正常运行了");
public static void main(String[] args) {
new SynchronizedTest().method1();
上面便是synchronized的重入锁特性,即调用method1()方法时,已经获得了锁,此时内部调用method2()方法时,由于本身已经具有该锁,所以可以再次获取。
public class ReentrantLockTest {
private Lock lock = new ReentrantLock();
public void method1() {
lock.lock();
System.out.println("方法1获得ReentrantLock锁运行了");
method2();
} finally {
lock.unlock();
public void method2() {
lock.lock();
System.out.println("方法1里面调用的方法2重入ReentrantLock锁,也正常运行了");
} finally {
lock.unlock();
public static void main(String[] args) {
new ReentrantLockTest().method1();
上面便是ReentrantLock的重入锁特性,即调用method1()方法时,已经获得了锁,此时内部调用method2()方法时,由于本身已经具有该锁,所以可以再次获取。
五、公平锁
CPU在调度线程的时候是在等待队列里随机挑选一个线程,由于这种随机性所以是无法保证线程先到先得的(synchronized控制的锁就是这种非公平锁)。但这样就会产生饥饿现象,即有些线程(优先级较低的线程)可能永远也无法获取CPU的执行权,优先级高的线程会不断的强制它的资源。那么如何解决饥饿问题呢,这就需要公平锁了。公平锁可以保证线程按照时间的先后顺序执行,避免饥饿现象的产生。但公平锁的效率比较低,因为要实现顺序执行,需要维护一个有序队列。
ReentrantLock便是一种公平锁,通过在构造方法中传入true就是公平锁,传入false,就是非公平锁。
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
以下是使用公平锁实现的效果:
public class LockFairTest implements Runnable{
//创建公平锁
private static ReentrantLock lock=new ReentrantLock(true);
public void run() {
while(true){
lock.lock();
System.out.println(Thread.currentThread().getName()+"获得锁");
lock.unlock();
public static void main(String[] args) {
LockFairTest lft=new LockFairTest();
Thread th1=new Thread(lft);
Thread th2=new Thread(lft);
th1.start();
th2.start();
输出结果:
Thread-1获得锁Thread-0获得锁Thread-1获得锁Thread-0获得锁Thread-1获得锁Thread-0获得锁Thread-1获得锁Thread-0获得锁Thread-1获得锁Thread-0获得锁Thread-1获得锁Thread-0获得锁Thread-1获得锁Thread-0获得锁Thread-1获得锁Thread-0获得锁
这是截取的部分执行结果,分析结果可看出两个线程是交替执行的,几乎不会出现同一个线程连续执行多次。
六、synchronized和ReentrantLock的比较
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。
总结:ReentrantLock相比synchronized,增加了一些高级的功能。但也有一定缺陷。在ReentrantLock类中定义了很多方法,比如:
//判断锁是否是公平锁
isLocked()
//判断锁是否被任何线程获取了
isHeldByCurrentThread()
//判断锁是否被当前线程获取了
hasQueuedThreads()
//判断是否有线程在等待该锁
2.两者在锁的相关概念上区别:
1)可中断锁顾名思义,就是可以相应中断的锁。
在Java中,synchronized就不是可中断锁,而Lock是可中断锁。如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。
lockInterruptibly()的用法体现了Lock的可中断性。
公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该锁(并不是绝对的,大体上是这种顺序),这种就是公平锁。
非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。
在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。ReentrantLock可以设置成公平锁。
读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。
正因为有了读写锁,才使得多个线程之间的读操作可以并发进行,不需要同步,而写操作需要同步进行,提高了效率。
ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。
可以通过readLock()获取读锁,通过writeLock()获取写锁。
4)绑定多个条件
一个ReentrantLock对象可以同时绑定多个Condition对象,而在synchronized中,锁对象的wait()和notify()或notifyAll()方法可以实现一个隐含的条件,如果要和多余一个条件关联的时候,就不得不额外地添加一个锁,而ReentrantLock则无须这么做,只需要多次调用new Condition()方法即可。
3.性能比较
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时ReentrantLock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
在JDK1.5中,synchronized是性能低效的。因为这是一个重量级操作,它对性能最大的影响是阻塞的是实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性带来了很大的压力。相比之下使用Java提供的ReentrankLock对象,性能更高一些。到了JDK1.6,发生了变化,对synchronize加入了很多优化措施,有自适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在JDK1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在未来的版本中还有优化余地,所以还是提倡在synchronized能实现需求的情况下,优先考虑使用synchronized来进行同步。
个人博客:/
微信公众号『码个蛋』特约作者详谈Lock与synchronized 的区别
投稿:jingxian
字体:[ ] 类型:转载 时间:
下面小编就为大家带来一篇详谈Lock与synchronized 的区别。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
1、lock是可中断锁,而synchronized 不是可中断锁
线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,
如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断
如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情
ReentrantLock获取锁定与三种方式:
a)& lock(),如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
b) tryLock(),如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
c) tryLock(long timeout,TimeUnit unit),如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断
2、synchronized是在JVM层面上实现的,lock是通过代码实现的,JVM会自动释放锁定(代码执行完成或者出现异常),但是使用Lock则不行,要保证锁定一定会被释放,就必须将unLock()放到finally{}中。
3、在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
以上这篇详谈Lock与synchronized 的区别就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具3733人阅读
JAVA(15)
& & &&&ReentrantLock&的lock机制有2种,忽略中断锁和响应中断锁,这给我们带来了很大的灵活性。比如:如果A、B2个线程去竞争锁,A线程得到了锁,B线程等待,但是A线程这个时候实在有太多事情要处理,就是 一直不返回,B线程可能就会等不及了,想中断自己,不再等待这个锁了,转而处理其他事情。这个时候ReentrantLock就提供了2种机制,第一,B线程中断自己(或者别的线程中断它),但是ReentrantLock&不去响应,继续让B线程等待,你再怎么中断,我全当耳边风(synchronized原语就是如此);第二,B线程中断自己(或者别的线程中断它),ReentrantLock&处理了这个中断,并且不再等待这个锁的到来,完全放弃。请看例子:
&* @version 0.10
&* @author Zhang Hua
public class Test {
&&& //是用ReentrantLock,还是用synchronized
&&& public static boolean useSynchronized =
&&& public static void main(String[] args) {
&&& &&& IBuffer buff =
&&& &&& if(useSynchronized){
&&& &&& &&& buff = new Buffer();
&&& &&& }else{
&&& &&& &&& buff = new BufferInterruptibly();&&&&
&&& &&& final Writer writer = new Writer(buff);
&&& &&& final Reader reader = new Reader(buff);
&&& &&& writer.start();
&&& &&& reader.start();
&&& &&& new Thread(new Runnable() {
&&& &&& &&& public void run() {
&&& &&& &&& &&& long start = System.currentTimeMillis();
&&& &&& &&& &&& for (;;) {
&&& &&& &&& &&& &&& // 等5秒钟去中断读
&&& &&& &&& &&& &&& if (System.currentTimeMillis() - start & 5000) {
&&& &&& &&& &&& &&& &&& System.out.println(&不等了,尝试中断&);
&&& &&& &&& &&& &&& &&& reader.interrupt();
&&& &&& &&& &&& &&& &&&
&&& &&& &&& &&& &&& }
&&& &&& &&& &&& }
&&& &&& &&& }
&&& &&& }).start();
interface IBuffer{
&&& public void write();
&&& public void read() throws InterruptedE
class Buffer implements IBuffer{
&&& private O
&&& public Buffer() {
&&& &&& lock =
&&& public void write() {
&&& &&& synchronized (lock) {
&&& &&& &&& long startTime = System.currentTimeMillis();
&&& &&& &&& System.out.println(&开始往这个buff写入数据…&);
&&& &&& &&& for (;;)// 模拟要处理很长时间
&&& &&& &&& {
&&& &&& &&& &&& if (System.currentTimeMillis() - startTime & Integer.MAX_VALUE)
&&& &&& &&& &&& &&&
&&& &&& &&& }
&&& &&& &&& System.out.println(&终于写完了&);
&&& public void read() {
&&& &&& synchronized (lock) {
&&& &&& &&& System.out.println(&从这个buff读数据&);
class BufferInterruptibly implements IBuffer{
&&& private ReentrantLock lock = new ReentrantLock();
&&& public void write() {
&&& &&& lock.lock();
&&& &&& try {
&&& &&& &&& long startTime = System.currentTimeMillis();
&&& &&& &&& System.out.println(&开始往这个buff写入数据…&);
&&& &&& &&& for (;;)// 模拟要处理很长时间
&&& &&& &&& {
&&& &&& &&& &&& if (System.currentTimeMillis() - startTime & Integer.MAX_VALUE)
&&& &&& &&& &&& &&&
&&& &&& &&& }
&&& &&& &&& System.out.println(&终于写完了&);
&&& &&& } finally {
&&& &&& &&& lock.unlock();
&&& public void read() throws InterruptedException{
&&& &&& lock.lockInterruptibly();// 注意这里,可以响应中断
&&& &&& try {
&&& &&& &&& System.out.println(&从这个buff读数据&);
&&& &&& } finally {
&&& &&& &&& lock.unlock();
class Writer extends Thread {
&&& private IB
&&& public Writer(IBuffer buff) {
&&& &&& this.buff =
&&& @Override
&&& public void run() {
&&& &&& buff.write();
class Reader extends Thread {
&&& private IB
&&& public Reader(IBuffer buff) {
&&& &&& this.buff =
&&& @Override
&&& public void run() {
&&& &&& try {
&&& &&& &&& buff.read();
&&& &&& } catch (InterruptedException e) {
&&& &&& &&& System.out.println(&我不读了&);&&&
&&& &&& System.out.println(&读结束&);
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:276656次
积分:2935
积分:2935
排名:第12442名
原创:17篇
转载:106篇
评论:46条
(2)(6)(1)(2)(1)(1)(1)(3)(8)(5)(14)(8)(2)(2)(2)(7)(23)(1)(34)
(window.slotbydup = window.slotbydup || []).push({
id: '4740881',
container: s,
size: '200,200',
display: 'inlay-fix'Java并发编程之显示锁Lock和ReentrantLock
Java并发编程之显示锁Lock和ReentrantLock
青城笑果果
Lock是一个接口提供了无条件的、可轮询的、定时的、可中断的锁获取操作,所有加锁和解锁的方法都是显示的。包路径是:java.util.concurrent.locks.Lock。核心方法是lock()、unlock()、tryLock(),实现类有ReentranLock、ReentrantReadWriteLock.ReadLock、ReentrantReadWriteLock.WriteLock。ReentrantLock是Lock的实现类,是一个互斥的同步器,它具有扩展的能力。在竞争条件下,ReentrantLock的实现要比现在的synchronized实现更具有可伸缩性。(有可能在JVM的将来版本中改进synchronized的竞争性能)这意味着当许多线程都竞争相同锁定是,使用ReentrantLock的吞吐量通常要比synchronized好。也就是说,当许多线程试图访问ReentrantLock保护的共享资源时,JVM将花费较少的时间来调度线程,而用更多时间执行线程。虽然ReentrantLock类有许多优点,但是与同步相比,它有一个主要缺点:他可能忘记释放锁定。ReentrantLock是在工作中对方法块加锁使用频率最高的。Lock与synchronized的比较:Lock使用起来比较灵活,但是必须有释放锁的动作配合。Lock必须手动释放和开启锁,而synchronized不需要手动释放和开启锁。Lock只适用于代码块,而synchronized对象之间是互斥关系。请注意以下两种方式的区别:第一种方式:两个方法之间的所是独立的。代码如下:运行结果如下(每次运行结果都是不一样的,仔细体会一下):第二种方式,两个方法之间使用相同的锁。ReentrantLockDemo类的内容不变,将CountTwo中的ReentrantLock改成全局变量,如下所示:运行结果如下(每次运行的结果是一样的,仔细体会一下):
本文仅代表作者观点,不代表百度立场。系作者授权百家号发表,未经许可不得转载。
青城笑果果
百家号 最近更新:
简介: 原创
作者最新文章

我要回帖

更多关于 锁和synchronized 的文章

 

随机推荐