这几个方法光知道是个啥,但理解的还不够深入,这里做一下总结。
在聊上面这个常见的方法之前,有必要先知道什么是管程。
当我们对临界区进行实现的时候,往往都是通过PV操作来实现的,但让程序员手动去做PV操作,很容易发生死锁。 所以为了方便编程,减少死锁出现的可能,我们希望能有一种数据结构或是软件模块来专门为我们提供对“临界区”的实现,这就是管程了~(但单单就说管程就是对临界区的实现是不准确的,继续往下看)
但仅仅是实现临界区还是不够的,比如,当线程A获取到锁了之后,进入了临界区,这个时候因为一些外部条件X, 而导致无法进行下去,这个时候就需要等待这个外部条件X的发生… 而假设这个外部条件X的发生是需要另一个线程B进入到当前的这个“临界区”中才能触发,而因为线程A已经处于临界区中了,所以线程B需要等待线程A退出临界区才能继续执行。。 于是。。就变成了线程A在等线程B,线程B在等线程A,死锁出现了。。
因此,解决临界区中的线程同步问题,也是管程需要实现的。
一个解决方案就是,在临界区中的线程A一旦发现自己想要的外部条件没有发生,而不能够继续进行下去了的时候,就主动释放掉当前获取的这个临界区的锁,然后让其他线程进入到这个临界区来触发这个“外部条件X”的发生。。 等到这个外部条件X发生了之后,再通知线程A(之前因这个条件而释放掉锁的那个线程)重新去竞争锁,继续执行临界区…
这个方法流程是不是很熟悉? 没错,这不就是wait和notify嘛。。
因此,管程的实现主要就是:
然后就可以了解下Java对管程的实现了。
Synchronized的同步块, ReentrantLock在lock和unlock期间的那段代码… 都是对临界区的实现…
在Java中,每个对象都有两个池,锁(monitor)池和等待池
锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁(因为wait()方法必须出现在synchronized中,这样自然在执行wait()方法之前线程A就已经拥有了该对象的锁),同时线程A就进入到了该对象的等待池中。如果另外的一个线程调用了相同对象的notifyAll()方法,那么处于该对象的等待池中的线程就会全部进入该对象的锁池中,准备争夺锁的拥有权。如果另外的一个线程调用了相同对象的notify()方法,那么仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的锁池.
对于Synchronized,只实现了wait和signal操作…
如果想使用更细粒度的条件变量,来控制临界区内线程的同步,那么可以使用ReentrantLock来做…
ReentrantLock提供了Condition变量,作为条件变量,对应的方法是 condition.await() 和 condition.signal()
在Java中,每个对象都有两个池,锁(monitor)池和等待池
锁池: 假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
等待池: 假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁(因为wait()方法必须出现在synchronized中,这样自然在执行wait()方法之前线程A就已经拥有了该对象的锁),同时线程A就进入到了该对象的等待池中。如果另外的一个线程调用了相同对象的notifyAll()方法,那么处于该对象的等待池中的线程就会全部进入该对象的锁池中,准备争夺锁的拥有权。如果另外的一个线程调用了相同对象的notify()方法,那么仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的锁池。
如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。
优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
必读:
sleep()和wait()方法与对象锁、锁池、等待池
Thread.yield()和Thread.sleep(0)
推荐阅读
我们在编程的时候必须能保证wait方法比notify方法先执行。如果notify方法比wait方法晚执行的话,就会导致因wait方法进入休眠的线程接收不到唤醒通知的问题。
而park、unpark则不会有这个问题,我们可以先调用unpark方法释放一个许可证,这样后面线程调用park方法时,发现已经许可证了,就可以直接获取许可证而不用进入休眠状态了。
LockSupport.park() 的实现原理是通过二元信号量做的阻塞,要注意的是,这个信号量最多只能加到1。我们也可以理解成获取释放许可证的场景。unpark()方法会释放一个许可证,park()方法则是获取许可证,如果当前没有许可证,则进入休眠状态,知道许可证被释放了才被唤醒。无论执行多少次unpark()方法,也最多只会有一个许可证。
另外,和wait方法不同,执行park进入休眠后并不会释放持有的锁。
并且,调用wait方法需要已经获取到锁,而park则不需要
使用wait的一个前提就是在sync的同步块里,而这又导致了在同步块里的条件变量只有一个,尽管可以通过共享变量的方式来实现“需要多个条件变量的场合”,但这样不仅实现的复杂度高,而且也不是很高效。因此,为了让在同步块中使用更多样的条件变量(即对某一资源或者某一个事件的等待),ReentrantLock就提供了Condition这一个神器,一个Lock可以new出多个Condition,即多个等待队列。
所以,await/signal, 可以看成强化版的 wait/notify
参考链接