java wait用法_JAVA并发-对象方法wait

最简单的东西,往往包含了最复杂的实现,因为需要为上层的存在提供一个稳定的基础,Object作为java中所有对象的基类,其存在的价值不言而喻,其中wait和notify方法的实现多线程协作提供了保证。

案例

public class WaitTestDemo {

public static void main(String[] args) {

Message msg = new Message("process it");

Waiter waiter = new Waiter(msg);

new Thread(waiter,"waiterThread").start();

Waiter waiter1 = new Waiter(msg);

new Thread(waiter1, "waiter1Thread").start();

Notifier notifier = new Notifier(msg);

new Thread(notifier, "notifierThread").start();

System.out.println("All the threads are started");

}

public static class Message {

private String msg;

public Message(String str){

this.msg=str;

}

public String getMsg() {

return msg;

}

public void setMsg(String str) {

this.msg=str;

}

}

public static class Waiter implements Runnable{

private Message msg;

public Waiter(Message m){

this.msg=m;

}

@Override

public void run() {

String name = Thread.currentThread().getName();

synchronized (msg) {

try{

System.out.println(name+" waiting to get notified at time:"+System.currentTimeMillis());

msg.wait();

}catch(InterruptedException e){

e.printStackTrace();

}

System.out.println(name+" waiter thread got notified at time:"+System.currentTimeMillis());

//process the message now

System.out.println(name+" processed: "+msg.getMsg());

}

}

}

public static class Notifier implements Runnable {

private Message msg;

public Notifier(Message msg) {

this.msg = msg;

}

@Override

public void run() {

String name = Thread.currentThread().getName();

System.out.println(name+" started");

try {

Thread.sleep(1000);

synchronized (msg) {

msg.setMsg(name+" Notifier work done");

msg.notify();

msg.notify();

//msg.notifyAll();

}

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

Output:

All the threads are started

waiterThread waiting to get notified at time:1572344152693

waiter1Thread waiting to get notified at time:1572344152693

notifierThread started

waiterThread waiter thread got notified at time:1572344153705

waiterThread processed: notifierThread Notifier work done

waiter1Thread waiter thread got notified at time:1572344153706

waiter1Thread processed: notifierThread Notifier work done

也可以使用notifyAll,输出为:

All the threads are started

waiterThread waiting to get notified at time:1572344222162

waiter1Thread waiting to get notified at time:1572344222162

notifierThread started

waiter1Thread waiter thread got notified at time:1572344223175

waiter1Thread processed: notifierThread Notifier work done

waiterThread waiter thread got notified at time:1572344223177

waiterThread processed: notifierThread Notifier work done

发现最后唤醒的顺序颠倒了

执行完notify方法,并不会立马唤醒等待线程,在notify方法后面加一段sleep代码就可以看到效果,如果线程执行完notify方法之后sleep 5s,在这段时间内,线程waiterThread1依旧持有monitor,线程waiterThread只能继续等待;

为什么要使用synchronized?

在Java中,synchronized有两种使用形式,同步方法和同步代码块。代码如下:

public class SynchronizedTest {

public synchronized void doSth(){

System.out.println("Hello World");

}

public void doSth1(){

synchronized (SynchronizedTest.class){

System.out.println("Hello World");

}

}

}

我们先来使用Javap来反编译以上代码,结果如下(部分无用信息过滤掉了):

public synchronized void doSth();

descriptor: ()V

flags: ACC_PUBLIC, ACC_SYNCHRONIZED

Code:

stack=2, locals=1, args_size=1

0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;

3: ldc #3 // String Hello World

5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

8: return

public void doSth1();

descriptor: ()V

flags: ACC_PUBLIC

Code:

stack=2, locals=3, args_size=1

0: ldc #5 // class com/hollis/SynchronizedTest

2: dup

3: astore_1

4: monitorenter

5: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;

8: ldc #3 // String Hello World

10: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

13: aload_1

14: monitorexit

15: goto 23

18: astore_2

19: aload_1

20: monitorexit

21: aload_2

22: athrow

23: return

反编译后,我们可以看到Java编译器为我们生成的字节码。在对于doSth和doSth1的处理上稍有不同。也就是说。JVM对于同步方法和同步代码块的处理方式不同。

对于同步方法,JVM采用ACC_SYNCHRONIZED标记符来实现同步。 对于同步代码块。JVM采用monitorenter、monitorexit两个指令来实现同步。

关于这部分内容,在JVM规范中也可以找到相关的描述。

同步方法

方法级的同步是隐式的。同步方法的常量池中会有一个ACC_SYNCHRONIZED标志。当某个线程要访问某个方法的时候,会检查是否有ACC_SYNCHRONIZED,如果有设置,则需要先获得监视器锁,然后开始执行方法,方法执行之后再释放监视器锁。这时如果其他线程来请求执行方法,会因为无法获得监视器锁而被阻断住。值得注意的是,如果在方法执行过程中,发生了异常,并且方法内部并没有处理该异常,那么在异常被抛到方法外面之前监视器锁会被自动释放。

同步代码块

同步代码块使用monitorenter和monitorexit两个指令实现。 The Java® Virtual Machine Specification 中有关于这两个指令的介绍:

大致内容如下: 可以把执行monitorenter指令理解为加锁,执行monitorexit理解为释放锁。 每个对象维护着一个记录着被锁次数的计数器。未被锁定的对象的该计数器为0,当一个线程获得锁(执行monitorenter)后,该计数器自增变为 1 ,当同一个线程再次获得该对象的锁的时候,计数器再次自增。当同一个线程释放锁(执行monitorexit指令)的时候,计数器再自减。当计数器为0的时候。锁将被释放,其他线程便可以获得锁。

归总

同步方法通过ACC_SYNCHRONIZED关键字隐式的对方法进行加锁。当线程要执行的方法被标注上ACC_SYNCHRONIZED时,需要先获得锁才能执行该方法。

同步代码块通过monitorenter和monitorexit执行来进行加锁。当线程执行到monitorenter的时候要先获得所锁,才能执行后面的方法。当线程执行到monitorexit的时候则要释放锁。

每个对象自身维护这一个被加锁次数的计数器,当计数器数字为0时表示可以被任意线程获得锁。当计数器不为0时,只有获得锁的线程才能再次获得锁。即可重入锁。

底层原理

对象头和内置锁(ObjectMonitor)

每个对象分为三块区域:对象头、实例数据和对齐填充。

对象头包含两部分,第一部分是Mark Word,用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等,这一部分占一个字节。第二部分是Klass Pointer(类型指针),是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,这部分也占一个字节。(如果对象是数组类型的,则需要3个字节来存储对象头,因为还需要一个字节存储数组的长度)

实例数据存放的是类属性数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐。

填充数据是因为虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。

级锁定、重量级锁定、GC标记、可偏向)下对象的存储内容如下表所示。

a0d992be14d3e58af6d0d5ea368c2d2f.png

Synchronized通常被称为重量级锁,但是1.6之后对其进行优化,新增了轻量级锁和偏向锁,这里重点说下重量级锁,随后对Synchronized的优化简单介绍下。

从对象头的存储内容可以看出锁的状态都保存在对象头中,Synchronized也不例外,当其从轻量级锁膨胀为重量级锁时,锁标识位为10,其中指针指向的是monitor对象(也称为管程或监视器锁)的起始地址。

关于Synchronized的实现在java对象头里较为简单,只是改变一下标识位,并将指针指向monitor对象的起始地址,其实现的重点是monitor对象。

在HotSpot虚拟机中,monitor采用ObjectMonitor实现。

内置锁(ObjectMonitor)

通常所说的对象的内置锁,是对象头Mark Word中的重量级锁指针指向的monitor对象,该对象是在HotSpot底层C++语言编写的(openjdk里面看),简单看一下代码:

//结构体如下

ObjectMonitor::ObjectMonitor() {

_header = NULL;

_count = 0;

_waiters = 0,

_recursions = 0; //线程的重入次数

_object = NULL;

_owner = NULL; //标识拥有该monitor的线程

_WaitSet = NULL; //等待线程组成的双向循环链表,_WaitSet是第一个节点

_WaitSetLock = 0 ;

_Responsible = NULL ;

_succ = NULL ;

_cxq = NULL ; //多线程竞争锁进入时的单向链表

FreeNext = NULL ;

_EntryList = NULL ; //_owner从该双向循环链表中唤醒线程结点,_EntryList是第一个节点

_SpinFreq = 0 ;

_SpinClock = 0 ;

OwnerIsThread = 0 ;

}

ObjectMonitor队列之间的关系转换可以用下图表示:

0f5d5981816501f0cc68564f66027eec.png

既然提到了_waitSet和_EntryList(_cxq队列后面会说),那就看一下底层的wait和notify方法

wait方法的实现过程:

//1.调用ObjectSynchronizer::wait方法

void ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {

/*省略 */

//2.获得Object的monitor对象(即内置锁)

ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj());

DTRACE_MONITOR_WAIT_PROBE(monitor, obj(), THREAD, millis);

//3.调用monitor的wait方法

monitor->wait(millis, true, THREAD);

/*省略*/

}

//4.在wait方法中调用addWaiter方法

inline void ObjectMonitor::AddWaiter(ObjectWaiter* node) {

/*省略*/

if (_WaitSet == NULL) {

//_WaitSet为null,就初始化_waitSet

_WaitSet = node;

node->_prev = node;

node->_next = node;

} else {

//否则就尾插

ObjectWaiter* head = _WaitSet ;

ObjectWaiter* tail = head->_prev;

assert(tail->_next == head, "invariant check");

tail->_next = node;

head->_prev = node;

node->_next = head;

node->_prev = tail;

}

}

//5.然后在ObjectMonitor::exit释放锁,接着 thread_ParkEvent->park 也就是wait

总结:通过object获得内置锁(objectMonitor),通过内置锁将Thread封装成OjectWaiter对象,然后addWaiter将它插入以_waitSet为首结点的等待线程链表中去,最后释放锁。

notify方法的底层实现

//1.调用ObjectSynchronizer::notify方法

void ObjectSynchronizer::notify(Handle obj, TRAPS) {

/*省略*/

//2.调用ObjectSynchronizer::inflate方法

ObjectSynchronizer::inflate(THREAD, obj())->notify(THREAD);

}

//3.通过inflate方法得到ObjectMonitor对象

ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {

/*省略*/

if (mark->has_monitor()) {

ObjectMonitor * inf = mark->monitor() ;

assert (inf->header()->is_neutral(), "invariant");

assert (inf->object() == object, "invariant") ;

assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is inva;lid");

return inf

}

/*省略*/

}

//4.调用ObjectMonitor的notify方法

void ObjectMonitor::notify(TRAPS) {

/*省略*/

//5.调用DequeueWaiter方法移出_waiterSet第一个结点

ObjectWaiter * iterator = DequeueWaiter() ;

//6.后面省略是将上面DequeueWaiter尾插入_EntrySet的操作

/**省略*/

}

总结:通过object获得内置锁(objectMonitor),调用内置锁的notify方法,通过_waitset结点移出等待链表中的首结点,将它置于_EntrySet中去,等待获取锁。注意:notifyAll根据policy不同可能移入_EntryList或者_cxq队列中,此处不详谈。

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值