synchronized
synchronized使用起来非常的方便,但是方便不等于简单,里面涉及的知识点还是挺多的,这里简单记录一点笔记。
首先记录一下我所认识的锁,任何对象在头信息里都有一个锁标记,类也是特殊的对象(class对象),同一时刻只能有一个线程能持有这个锁,当用synchronized时,线程会获取锁,别的线程想要获取这个锁时只能等待这个线程释放锁。
这两天看了很多人的文章,讲的都大同小异,说到synchronized的用法,有的说三种,有的说四种,有的说五种,也不知道官方是怎么定义的,我就把所有的都罗列一下:
锁代码块 | synchronized(this) | 对象锁 |
synchronized(object) | 对象锁 | |
synchronized(class) | 类锁 | |
锁方法 | synchronized void method() | 对象锁 |
static synchronized void method() | 类锁 |
同一个对象锁/类锁,同一时刻只能被一个线程获取。对于小白玩家,这些就是大概了,再多的了解可以看《深入理解Java虚拟机》、《Java编程思想》等书,https://www.cnblogs.com/zaizhoumo/p/7700161.html 这篇博客也不错。
wait、notify、notifyAll
这三个一般都放在一块儿说,因为都是在synchronized的同步块中使用的。(思考一下为什么必须在同步块中使用?)
锁的管理是通过Monitor来完成的,Monitor维护着WaitSet和EntryList、owner。WaitSet里的线程必须被notify/notifyAll唤醒,才能进入EntryList,EntryList中的线程准备争夺锁并等待锁被释放,owner是当前获得锁的线程,owner的线程在wait()方法之后会进入WaitSet。所以这三个状态是互相转换的。
notify和notifyAll的区别从字面可以理解,notify是只唤醒一个WaitSet中的线程进入EntryList,notifyAll是唤醒全部WaitSet中的线程进入EntryList。假设WaitSet中有5个线程等待被唤醒,notify之后,WaitSet中还剩4个线程,被唤醒的线程参与锁的竞争;notifyAll之后,WaitSet中的线程全部被唤醒,参与锁的竞争。
接前面的问题,为什么这三个方法必须在同步块中使用?因为owner是当前获得对象锁的线程,如果没有获得锁,就没法继续后面的wait、notify、notifyAll,会报"java.lang.IllegalMonitorStateException:current thread not owner"异常。
小tips:永远在循环中调用wait()
wait之后的线程被谁唤醒是不可知的,假设一个场景:“多线程生产,多线程消费”。
synchronized(this) {
if (empty) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
remove();
notifyAll();
}
synchronized(this) {
while(empty) {//被唤醒后再次检查条件
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
remove();
notifyAll();
}
这里有两段代码,一个是if判断,一个是while循环判断。消费者发现empty条件成立时会进入等待,因为生产者和消费者都是多个线程,如果唤醒这个消费者线程的是别的消费者线程,那么第一段代码就会走到remove()方法,可能会出现意料之外的异常。而第二段代码在线程被唤醒后还会再次判断,保证了remove()的前置条件。