Java多线程(2)---synchronized、wait、notify

本文深入解析Java中的synchronized关键字,探讨其作为同步锁的作用机制,包括修饰成员方法、静态方法及代码块的不同应用场景,同时对比synchronized与wait、notify、sleep等方法的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Synchronized关键字:

synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
         1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
         2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
         3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;

synchronized关键字是不能继承的,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要显式的指定它的某个方法为synchronized方法;

1、synchronized修饰成员方法

public synchrnized void method(...) {......}

输出结果:

2、synchronized修饰静态方法:

public synchronized static method(...) {......}

3、synchronized修饰代码块

synchronized(Object o){......}

synchronized(Object.class) {......}

三种方式比较:

synchronized修饰成员方法,锁的是当前类的对象,等同于synchronized(this){......};

synchronized修饰静态方法,锁的事当前的类对象,等同于synchronized(Object.class){......} 

synchronized锁机制简介:

synchronized是基于进入和退出管程对象monitor实现的。

对象的内存简图:

  • 对象头:存储对象的hashCode、锁信息或分代年龄、GC标志,类型指针指向对象的类元数据,JVM通过通过类型指针确定对象的类信息。
  • 实例变量:存放类的属性数据信息,包括父类的属性信息
  • 填充数据:非必须存在,仅仅为了字节对齐

当对象加锁时,数据记录在对象头中。当执行synchronized同步方法或同步代码块时,会在对象头中记录锁标记,锁标记指向monitor对象。每个对象都存在一个monitor与之关联,对象与其对对应的monitor之间的关系有多种实现方式,如monitor可以与对象一起创建,或者当线程试图获取对象锁时自动生成,但当一个monitor被某个线程持有后,它就处于锁定状态。

在JVM中,monitor是被ObjectMonitor实现的。ObjectMonitor中有两个队列,_WaitSet 和 _EntryList,以及_Owner标记。其中_WaitSet用于管理等待队列(wait)线程,_EntryList用于管理锁池阻塞线程,_Owner标记用于记录当前执行线程。如下图:

当多线程并发访问同一同步代码时,首先会进入_EntryList,当线程获取锁标记后,monitor中的_Owner记录此线程,并在monitor中的计数器执行递增计算,代表锁定。其他线程在_EntryList中继续阻塞。若执行线程调用wait方法,则monitor中的计数器将被赋值为0,并将_Owner标记赋值为null,代表放弃锁。执行线程进入_WaitSet中阻塞。若执行线程调用notify/notifyAll方法,_WaitSet中的线程被唤醒,进入_EntryList中阻塞,等待获取锁标记。若执行线程的同步代码执行结束,同样会释放锁标记,monitor中的_Owner标记赋值为null,并且计数器赋值为0.

wait、notify、sleep:

wait()、notify()、notifyAll()是Object类的成员方法,sleep()是Thread类的类方法。

  •    final void notify()  唤醒在此对象监视器上等待的单个线程。 
  •    final void notifyAll()  唤醒在此对象监视器上等待的所有线程。
  •    final void wait()   在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
  •    final void wait(long timeout)   在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。
  • static void sleep(long millis)  在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响 

输出结果:

在Thread1的同步方法中,调用wait()方法后,线程开始进入阻塞状态。在Runnable的同步方法中调用notifyAll()方法,唤醒等待的线程后,Thread1中的同步方法开始执行,而Runnabe中的同步方法,由于在唤醒其他等待的线程后,调用了sleep()方法,休眠了0.2S,这段时间内它不参与CPU资源的争夺,直到休眠时间结束。

  • sleep()方法,正在执行的线程主动让出CPU去执行其他线程,在sleep()方法指定的时间过后,CPU才会回到这个线程上继续往下执行,如果当前线程进入了同步锁,sleep()方法并不会释放锁,即使当前线程使用sleep()方法让出了CPU,但其他被同步锁挡住了的线程也无法得到执行。
  •  wait()在一个已经进入了同步锁的线程内进行调用,让当前线程暂时让出同步锁,以便其他正在此锁的线程可以得到同步锁并运行。当其他线程调用了notify()方法后,调用wait()方法的线程就会解除wait状态,当再次获得同步锁后,程序可以继续向下执行。

即:如果被thread2先获取锁,则thread1在这0.2S内也是无法获取CPU资源的,这时输出的结果是:

如果想让thread1执行,thread2等待,只需更改代码如下:

输出结果:

注意:调用wait()、notify()、notifyAll()方法时,可能会抛出异常 IllegalMonitorStateException。因为调用wait(),notify()和notifyAll()的线程在调用这些方法前必须"拥有"对象的锁。当前的线程不是此对象锁的所有者,却调用该对象的notify(),notify(),wait()方法时抛出该异常。如上面调用wait()方法时,不用o.wait(),而直接用wait(),等价于this.wait(),但是锁对象并不是this,而是Object o , 这再运行时就会抛出异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值