笔记08Java的线程同步Synchronized和ReentrantLock

作者:神秘网友 发布时间:2021-01-12 20:22:20

笔记08Java的线程同步Synchronized和ReentrantLock

Synchronized

synchronized可以用来修饰以下3个层面:

  • 1. 修饰实例的方法;
  • 2. 修饰静态类的方法;
  • 3. 修饰代码块

synchronized修饰实例方法:

笔记08Java的线程同步Synchronized和ReentrantLock

synchronized修饰实例方法的时候,锁对象是当前的实例对象,同一个实例调用此方法的时候才会产生互斥效果,不同的实例对象之间不会有互斥效果。

笔记08Java的线程同步Synchronized和ReentrantLock

上面的代码中,在不同的线程中调用不同对象的printLog方法,两者相互不排斥,两个线程会随机竞争CPU资源:

笔记08Java的线程同步Synchronized和ReentrantLock

上面的打印效果可以看出,两个线程的执行互不影响,打印信息是随机的。

如果将代码改成两个线程公用一个对象进行printLog:

笔记08Java的线程同步Synchronized和ReentrantLock

代码执行的效果如下:

笔记08Java的线程同步Synchronized和ReentrantLock

可以看出只有在一个线程执行完毕之后,另一个线程的调用才会执行,不同线程中的调用是相互排斥的。

修饰静态类方法

如果synchronized修饰的静态类的方法,那么锁对象就是当前Class。在不同的线程中调用不同的实例对象,也会有互斥的效果。

下面将printLog修改为静态方法:

笔记08Java的线程同步Synchronized和ReentrantLock

执行代码进行打印:

笔记08Java的线程同步Synchronized和ReentrantLock

上面的打印结果可以看出,两个线程是依次执行的。

synchronized修饰代码块

笔记08Java的线程同步Synchronized和ReentrantLock

synchronized作用于代码块的时候,锁对象是跟在synchronized后面的对象。任何的对象都可以当作synchronize的锁对象。

实现细节

synchronized既可以作用于方法,也可以作用于代码块。但不同的锁对象的实现是有区别的。

下面的代码中,synchronized作用于代码块上:

笔记08Java的线程同步Synchronized和ReentrantLock

使用javap查看对应的字节码:

笔记08Java的线程同步Synchronized和ReentrantLock

从上面编译好的字节码文件可以看出,synchronized修饰代码块的时候,会把被修饰的代码用monitorenter和monitorexit进行包裹。另外字节码中有一个monitorenter指令,和两个monitorexit指令。这是虚拟机为了保证在异常发生的情况下,锁也能够被释放,所以两个monitorexit,一个是正常流程释放锁,另一个是异常流程释放锁。

当synchronized修饰方法的时候

笔记08Java的线程同步Synchronized和ReentrantLock

从上面的代码和字节码中可以看到,synchronized修饰方法的时候,代码编译时会在方法的flags中标示ACC_SYNCHRONIZED标志。当虚拟机访问一个ACC_SYNCHRONIZED方法的时候,会自动在方法的开始和结束的位置添加monitorenter和monitorexit指令。

monitorenter和monitorexit指令可以理解为一把锁,这个锁有两个重要的属性:计数器和指针。

  • 计数器:当前线程访问锁的次数;
  • 指针:指向持有这把锁的线程。
笔记08Java的线程同步Synchronized和ReentrantLock

锁计数器默认为0,当执行monitorenter指令的时候,如果这把锁的计数器为0,说明这把锁没有被任何线程锁持有,此时线程会将锁计数器加1,并将锁的指针指向自己。当执行monitorexit的时候,会将计数器减1。

ReentrantLock

ReentrantLock和synchronized不同,ReentrantLoock的加锁解锁都是需要手动完成的:

笔记08Java的线程同步Synchronized和ReentrantLock

上面的代码中lock和unlock分别是加锁和解锁的过程。

笔记08Java的线程同步Synchronized和ReentrantLock

上面的打印可以看出ReentrantLock可以实现和synchronized一样的效果。

【注意】ReentrantLock的加锁和解锁需要手动完成,为了保证在异常流程中也能够成功的解锁,我们需要在try-catch的finally中解锁,从而保证任何时候锁都可以被正常释放。

公平锁

ReentrantLock有一个带参数的构造函数:

笔记08Java的线程同步Synchronized和ReentrantLock

默认情况下,synchronized和ReentrantLock都是非公平锁。但是ReentrantLock可以通过传入fair = true来创建一个公平锁。公平锁是通过一个同步队列来实现多线程按申请锁的顺序获得锁。

笔记08Java的线程同步Synchronized和ReentrantLock

运行代码:

笔记08Java的线程同步Synchronized和ReentrantLock

【分析】

  • 1. 3个线程依次start,一个线程持有锁之后,另外两个线程就会被加入到ReentrantLock的同步队列中,等到当前持有锁的线程进行一次自加之后,锁被释放,此时由同步队列中下一个线程持有锁;
  • 2. 上面代码的run方法中有sharedNumber 20的判断,但是打印结果会打印到22。三个线程,在争夺CPU资源的时候,同时经过while的判断进入了while内部,到lock.lock()的时候,只有一个线程持有锁并获得了操作sharedNumber的机会,等这个线程执行完毕释放锁的时候,另外两个线程由于已经通过while判断,所以不会再次进行while中的sharedNumber的判断,因此会形成这种情况。要是为了避免这种情况的发生,可以在lock.lock()之后,真正操作sharedNumber的时候再加一层sharedNumber 20的判断。

读写锁(ReentrantReadWriteLock)

concurrent包中提供了ReentrantReadWriteLock,在读操作的时候获取读锁,在写操作的时候获得写锁。

1. 创建一个读写锁:

ReadWriteLock rwLock = new ReentrantReadWriteLock();

2. 通过rwLock对象分别获得读锁(ReadLock)和写锁(WriteLock):

ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock(); 
ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();

3. 使用读锁和写锁:

笔记08Java的线程同步Synchronized和ReentrantLock

当写入操作在执行的时候,读取数据的操作会被阻塞,当写入操作执行完毕之后,肚脐数据的操作继续执行,并且读取的数据是最新写入的数据;多个读操作之间是互不排斥的。

?

笔记08Java的线程同步Synchronized和ReentrantLock 相关文章

  1. 笔记09线程池刨根问底

    我们知道CPU运行的最小单位是线程,Java中实现并发是通过多线程来完成的,利用多线程提高了对CPU资源的利用率,但是线程的创建和销毁是很消耗性能的。线程的创建伴随着虚拟机栈、本地方法栈、程序计数器等线程私有内存空间的创建,在线程销毁的时候也伴随着

  2. 线程的消息队列是怎么创建的

    可以在子线程中创建handler么? 主线程的Looper和子线程的Looper有什么区别? Looper和MessageQueue有什么关系? MessageQueue是怎么创建的? 上图中我们在子线程中创建一个Handler对象,会抛出异常。异常信息说明不能在没有调用Looper.prepare()的线程中创

  3. 笔记10DVM或ART是如何对JVM进行优化的

    DVM大多数实现和传统的JVM相似,但是为了满足Android在手机端内存的限制,Dalvik对JVM做了一些独有的优化。 Dex文件 传统的class文件是由Java源码文件编译生成的,而Android在编译打包的时候,会将所有的class文件整合优化,最终生成class.dex文件。dex文件

  4. Android线程间消息传递机制

    上图是消息循环的过程,当线程进入Looper.loop()循环之后,会从MessageQueue中阻塞的读取Message,要是MessageQueue中没有消息,会一直阻塞在queue.next的地方,直到从MessageQueue中读取到Message,然后将该Message分发给Message的target,这个target是一个

  5. R语言进阶笔记5purrr替代循环

    purrr 替代循环 1 purrr循环 引用知乎张敬信的说法: ?用 R 写 「循环」 从低到高有三种境界:手动 for 循环,apply 函数族,purrr 包泛函式编程。? R写循环有三个境界: 手动for循环 apply循环 purrr泛函式编程 其中,手动for循环我最常用,apply系列半吊子

  6. R语言进阶笔记4dplyr 汇总统计

    之前写过一篇博文(汇总统计?一个函数全部搞定!),介绍R中编写一个函数,进行汇总统计。效果很不错。今天用 tidyverse 包实现一下,多角度尝试,然后尝试中学习。 1. 想要达到的效果 最近,一个朋友让我帮忙做一个图标,是这个样子的: 相关的统计参数:

  7. 【计算机网络】学习笔记第六篇:应用层(谢希仁版)

    说明: 文章主要针对科班上课做的简单笔记,以及后面针对一些面试涉及内容的详细讲解 再三声明:考研的同学好好去背书,做题,重点研究一下侧重点,加油 !!! 此文章归纳整理自:【计算机网络】(第七版)谢希仁 ,一切内容版权均归书籍作者所有,侵删 一

  8. 【计算机网络】学习笔记第五篇:运输层(谢希仁版)

    说明: 文章主要针对科班上课做的简单笔记,以及后面针对一些面试涉及内容的详细讲解 再三声明:考研的同学好好去背书,做题,重点研究一下侧重点,加油 !!! 此文章归纳整理自:【计算机网络】(第七版)谢希仁 ,一切内容版权均归书籍作者所有,侵删 一

  9. 云原生技术之docker学习笔记(2)

    // 云原生技术之docker学习笔记(2) // 今天我们看看Docker镜像相关内容。 Docker镜像说明 1、什么是Docker镜像? docker镜像是由多个文件系统叠加而成的,最底层是一个引导文件系统,也就是bootfs,用户基本上不会和底层的引导文件系统有任何交互。所以这块

  10. 云原生技术之docker学习笔记(1)

    // 云原生技术之docker学习笔记(1) // 最近的工作中,MySQL和MongoDB运维部分的内容比较少。主要工作内容都会频繁接触docker和k8s相关的内容,所以准备写写这块儿的内容。 之前的文章中,介绍过docker上部署MySQL的过程,这里贴出来链接,方便回顾: docker

每天更新java,php,javaScript,go,python,nodejs,vue,android,mysql等相关技术教程,教程由网友分享而来,欢迎大家分享IT技术教程到本站,帮助自己同时也帮助他人!

Copyright 2020, All Rights Reserved. Powered by 跳墙网(www.tqwba.com)|网站地图|关键词