Java多线程之synchronized同步代码块详解
作者:小小茶花女 发布时间:2022-02-10 21:29:51
面试题:
1同步方法和同步块,哪种更好?
2.如果同步块内的线程抛出异常会发生什么?
1. 同步方法和同步块,哪种更好?
同步块更好,这意味着同步块之外的代码是异步执行的,这比同步整个方法更提升代码的效率。请知道一条原则:同步的范围越小越好。
对于小的临界区,我们直接在方法声明中设置synchronized同步关键字,可以避免竞态条件的问题。但是对于较大的临界区代码段,为了执行效率,最好将同步方法分为小的临界区代码段。
public class TwoPlus {
private int num1 = 0;
private int num2 = 0;
public synchronized void plus(int val1,int val2){
this.num1 = num1+val1;
this.num2 = num2+val2;
}
}
临界区代码段包含对两个临界区资源的操作,这两个临界区资源分别为sum1和sum2。使用synchronized对plus(int val1,int val2)进行同步保护之后,进入临界区代码段的线程拥有sum1和sum2的操作权,并且是全部占用。一旦线程进入,当线程在操作sum1而没有操作sum2时,也将sum2的操作权白白占用,其他的线程由于没有进入临界区,只能看着sum2被闲置而不能去执行操作。
所以,将synchronized加在方法上,如果其保护的临界区代码段包含的临界区资源多于一个,就会造成临界区资源的闲置等待,进而会影响临界区代码段的吞吐量。为了提升吞吐量,可以将synchronized关键字放在函数体内,同步一个代码块。synchronized同步块的写法是:
synchronized (syncObject){
// 临界区代码段的代码块
}
在synchronized同步块后边的括号中是一个syncObject对象,代表着进入临界区代码段需要获取syncObject对象的监视锁,由于每一个Java对象都有一把监视锁,因此任何Java对象都能作为synchronized的同步锁。
使用synchronized同步块对上面的TwoPlus类进行吞吐量的提升改造,具体的代码如下:
public class TwoPlus {
private int num1 = 0;
private int num2 = 0;
// 两把不同的锁对象
private Object object1 = new Object();
private Object object2 = new Object();
public void plus(int val1,int val2){
synchronized (object1){
this.num1 = num1+val1;
}
synchronized (object2){
this.num2 = num2+val2;
}
}
}
改造之后,对两个独立的临界区资源sum1和sum2的加法操作可以并发执行了,在某一个时刻,不同的线程可以对sum1和sum2同时进行加法操作,提升了plus()方法的吞吐量。
synchronized
方法和synchronized
同步块有什么区别呢?
总体来说,synchronized方法是一种粗粒度的并发控制,某一时刻只能有一个线程执行该synchronized方法;而synchronized代码块是一种细粒度的并发控制,处于synchronized块之外的其他代码是可以被多个线程并发访问的。在一个方法中,并不一定所有代码都是临界区代码段,可能只有几行代码会涉及线程同步问题。所以synchronized代码块比synchronized方法更加细粒度地控制了多个线程的同步访问。
synchronized方法的同步锁实质上使用了this对象锁,这样就免去了手工设置同步锁的工作。而使用synchronized代码块需要手工设置同步锁。
2. synchronized同步代码块
public class RoomDemo {
private static int count = 0;
// 创建锁对象,同步代码块需要手动设置对象锁
private static Object object = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for(int i=0;i<5000;i++){
// 使用object对象锁住临界区资源
synchronized (object){
count++;
}
}
},"t1");
Thread t2 = new Thread(()->{
// 使用object对象锁住临界区资源
for(int i=0;i<5000;i++){
synchronized (object){
count--;
}
}
},"t2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);// 0
}
}
你可以做这样的类比: synchronized(对象) 中的对象,可以想象为一个房间,线程 t1,t2 想象成两个人
(1) 当线程 t1 执行到 synchronized(object) 时就好比 t1 进入了这个房间,并锁住了门拿走了钥匙,在门内执行 count++ 代码 ;
(2) 这时候如果 t2 也运行到了 synchronized(object) 时,它发现门被锁住了,只能在门外等待,发生了上下文切换,阻塞住了 ;
(3) 这中间即使 t1 的 cpu 时间片不幸用完,被踢出了门外 (不要错误理解为锁住了对象就能一直执行下去哦) , 这时门还是锁住的,t1 仍拿着钥匙,t2 线程还在阻塞状态进不来,只有下次轮到 t1 自己再次获得时间片时才 能开门进入
(4) 当 t1 执行完 synchronized{} 块内的代码,这时候才会从 obj 房间出来并解开门上的锁,唤醒 t2 线程并把钥匙给他。t2 线程这时才可以进入 obj 房间,锁住了门拿上钥匙,执行它的 count-- 代码;
3. 如果同步块内的线程抛出异常会发生什么?
public class ExceptionDemo {
private static int count = 1;
// 创建锁对象
private static Object object = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
synchronized (object){
System.out.println("线程t1正在执行");
// 死循环
while (count==1){
}
}
},"t1");
Thread t2 = new Thread(()->{
synchronized (object) {
System.out.println("线程t2正在执行");
count--;
}
},"t2");
t1.start();
t2.start();
t1.join();
t2.join();
}
}
执行结果:
可以看出线程t1执行的是死循环,所以每次线程上下文切换,线程t2都被阻塞了,拿不到锁,从而无法执行。
假如我们在线程执行过程中制造一个异常:
public class ExceptionDemo {
private static int count = 1;
// 创建锁对象
private static Object object = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
synchronized (object){
System.out.println("线程t1正在执行");
while (count==1){
// 死循环中制造异常
Integer.parseInt("a");
}
}
},"t1");
Thread t2 = new Thread(()->{
synchronized (object) {
System.out.println("线程t2正在执行");
count--;
}
},"t2");
t1.start();
t2.start();
t1.join();
t2.join();
}
}
执行结果:
当持有锁对象的线程在执行同步代码快中的代码时,如果出现异常,会释放锁,从而线程t2就可以拿到锁对象去执行自己同步代码块中的代码了。
来源:https://hengheng.blog.csdn.net/article/details/123116996
猜你喜欢
- TM的作用我们根据源码解读画出了下图,该图示展现了TM在整个Seata AT模式的分布式事务中所起的作用:从上图中可以看出,TM主要有两个作
- 很多时候你新建了Maven 或者SpringBoot 工程,激动的点了主启动类,你就发现了下面的错误这里说的是啥意思呢,你没有数据库相关的链
- 如何实现 WPF 代码查看器控件框架使用.NET40;Visual Studio 2019;代码展示需要使用到AvalonEdit是基于WP
- 微信公众号开发之回复图文消息,供大家参考,具体内容如下图文消息的主要参数说明通过微信官方的消息接口指南,可以看到对图文消息的参数介绍,如下图
- java API中提供了一个基于指针操作实现对文件随机访问操作的类,该类就是RandomAccessFile类,该类不同于其他很多基于流方式
- Java BigDecimal的坑采坑处 BigDecimal bd =new BigDecimal(0.1);
- 一、ObjectContext对象上下文Entity SQL 语言 - ADO.NET | Microsoft 官当文档ObjectCont
- 项目前端由于采用Extjs4,列表分页需要返回三个参数:totalCount(记录总数)、start(开始位置)、limit(每页条数)。由
- 发送虚拟请求访问controller我们在test类中虚拟访问controller,就得发送虚拟请求。先创建一个controllerpack
- 一、logback日志技术介绍Spring Boot中使用的日志技术为logback。其与Log4J都出自同一人,性能要优于Log4J,是L
- 当图像信息量较大,采用以上直接显示的方法,可能前面一部分显示后,显示后面一部分时,由于后面一部分还未从文件读出,使显示呈斑驳现象。为了提高显
- 一、前言首选,双轴快排也是一种快排的优化方案,在JDK的Arrays.sort()中被主要使用。所以,掌握快排已经不能够满足我们的需求,我们
- 迪杰斯特拉算法迪杰斯特拉算法是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算
- 定义JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动
- Lua是目前国内使用最多的热更语言,基于Lua的热更框架也非常多,最近学习了一下ToLua的热更框架,主要使用的问题在于C#和Lua之间的互
- 一、Thread.start()与Thread.run()的区别通过调用Thread类的start()方法来启动一个线程,这时此线程是处于就
- 本文实例讲述了JAVA设计模式之建造者模式定义与用法。分享给大家供大家参考,具体如下:建造者模式:将复杂对象的构造与它的实现相分离,让相同的
- 一般很多项目不是在springcloud的环境中使用的,但是需要用到分布式配置中心来管理一些外部或者项目的配置,这个时候我们可以使用spri
- 本文实例讲述了Java实现数据库连接池的方法。分享给大家供大家参考。具体如下:package com.kyo.connection;impo
- 1.取整运算符取整从字面意思理解就是被除数到底包含几个除数,也就是能被整除多少次,那么它有哪些需要注意的地方呢?先看下面的两端代码: &nb