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
   }
}

Java多线程之synchronized同步代码块详解

你可以做这样的类比: 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-- 代码;

Java多线程之synchronized同步代码块详解

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();
   }
}

执行结果:

Java多线程之synchronized同步代码块详解

可以看出线程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();
   }
}

执行结果:

Java多线程之synchronized同步代码块详解

当持有锁对象的线程在执行同步代码快中的代码时,如果出现异常,会释放锁,从而线程t2就可以拿到锁对象去执行自己同步代码块中的代码了。

来源:https://hengheng.blog.csdn.net/article/details/123116996

标签:Java,synchronized,同步,代码块
0
投稿

猜你喜欢

  • C#实现支付宝沙箱支付的项目实践

    2021-07-29 17:18:18
  • Java验证时间格式是否正确方法类项目实战

    2021-05-30 02:15:15
  • 浅谈JVM垃圾回收有哪些常用算法

    2022-02-28 16:51:56
  • JAVA字符串占位符使用方法实例

    2021-09-20 17:30:27
  • WPF基于物理像素绘制图形

    2022-01-06 20:25:18
  • IDEA的默认快捷键设置与Eclipse的常用快捷键的设置方法

    2023-04-09 18:32:40
  • 详解Java实现批量压缩图片裁剪压缩多种尺寸缩略图一键批量上传图片

    2022-12-07 15:56:04
  • C#对Word文档的创建、插入表格、设置样式等操作实例

    2021-10-24 23:13:46
  • Maven安装及MyEclipse中使用Maven

    2023-06-20 04:29:07
  • 基于Java实现经典蜘蛛纸牌游戏

    2021-09-06 15:22:57
  • 使用栈的迷宫算法java版代码

    2022-03-07 12:47:16
  • 属于自己的Android对话框(Dialog)自定义集合

    2022-08-03 23:09:21
  • c#委托与事件(详解)

    2022-12-06 04:50:47
  • C#微信公众号开发 微信事件交互

    2023-04-22 21:18:31
  • 浅析Android文件存储

    2023-06-16 09:07:37
  • 两个surfaceView实现切换效果

    2021-10-04 15:18:52
  • spring data jpa分页查询示例代码

    2023-01-09 14:51:50
  • 深入浅析Android消息机制

    2023-07-26 03:20:02
  • Java实现把两个数组合并为一个的方法总结

    2022-08-04 16:30:03
  • springboot+vue实现登录功能的最新方法整理

    2022-08-31 21:40:23
  • asp之家 软件编程 m.aspxhome.com