在线程调度器的幕后,有些东西从Java8变成了Java9。我正试图缩小以下程序的变化范围。
下面的程序生成3个线程,它们并行地运行,并同步地通过监视器锁,打印。
Aa0Bb1Cc2Dd3.......Zz25当前代码在所有Java版本中都运行良好,我不需要任何优化。
在使用Object.notifyAll()传递锁之前,我使用了Object.wait() (这可能并不总是正确的,但在这种情况下,它在java1.8中并没有起到作用)。这就是为什么有两个版本的这个代码版本1和版本2。
第1版在所有java版本(Java8和优先版本、Java9及更高版本)中运行良好。但不是第2版。例如,当您评论版本1和不注释版本2时,如下所示
//obj.wait();//version 1
obj.notifyAll();obj.wait();//version 2它在Java8中的运行与在Java9和以后的JDK中完全一样。它无法抓住锁,或者抓住了锁,但是条件已经翻转,没有线程了。
(例如,假设麻木线程完成了它的工作,现在只有能够捕获锁并继续执行的线程才是ThreadCapital,但不知怎么地,isCapital变成了假的--这只是一个推测,不能证明这一点,或者说不能确定这是否发生了。)
我很少有使用线程的经验,所以我确信我没有利用监视器上的锁,即使我有它,也应该在所有JDK中反映同样的情况。除非在Java9和更高版本中发生了一些变化。线程调度程序内部有什么变化吗?
package Multithreading_misc;
public class App {
public static void main(String[] args) throws InterruptedException {
SimpleObject obj = new SimpleObject();
ThreadAlphaCapital alpha = new ThreadAlphaCapital(obj);
ThreadAlphaSmall small = new ThreadAlphaSmall(obj);
ThreadNum num = new ThreadNum(obj);
Thread tAlpha = new Thread(alpha);
Thread tSmall = new Thread(small);
Thread tNum = new Thread(num);
tAlpha.start();
tSmall.start();
tNum.start();
}
}
class ThreadAlphaCapital implements Runnable{
char c = 'A';
SimpleObject obj;
public ThreadAlphaCapital(SimpleObject obj){
this.obj = obj;
}
@Override
public void run() {
try {
synchronized (obj) {
while(c < 'Z')
{
if(!obj.isCapitalsTurn || obj.isNumsTurn)
{
obj.wait();//version 1
//obj.notifyAll();obj.wait();//version 2
}
else
{
Thread.sleep(500);
System.out.print(c++);
obj.isCapitalsTurn = !obj.isCapitalsTurn;
obj.notifyAll();//version 1
//obj.notifyAll();obj.wait();//version 2
}
}
obj.notifyAll();
}
}
catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
class ThreadAlphaSmall implements Runnable{
char c = 'a';
SimpleObject obj;
public ThreadAlphaSmall(SimpleObject obj){
this.obj = obj;
}
@Override
public void run() {
try {
synchronized (obj) {
while(c < 'z')
{
if(obj.isCapitalsTurn || obj.isNumsTurn)
{
obj.wait();//version 1
//obj.notifyAll();obj.wait();//version 2
}
else
{
Thread.sleep(500);
System.out.print(c++);
obj.isCapitalsTurn = !obj.isCapitalsTurn;
obj.isNumsTurn = !obj.isNumsTurn;
obj.notifyAll();//version 1
//obj.notifyAll();obj.wait();//version 2
}
}
obj.notifyAll();
}
}
catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
class ThreadNum implements Runnable{
int i = 0;
SimpleObject obj;
public ThreadNum(SimpleObject obj){
this.obj = obj;
}
@Override
public void run() {
try {
synchronized (obj) {
while(i < 26)
{
if(!obj.isNumsTurn)
{
obj.wait();//version 1
//obj.notifyAll();obj.wait();//version 2
}
else
{
Thread.sleep(500);
System.out.print(i++);
obj.isNumsTurn = !obj.isNumsTurn;
obj.notifyAll();//version 1
//obj.notifyAll();obj.wait();//version 2
}
}
obj.notifyAll();
}
}
catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
class SimpleObject{
public boolean isNumsTurn = false;
public boolean isCapitalsTurn = true;
}注意事项少:
发布于 2019-03-11 15:31:26
我相信过度通知。
目前还不清楚为什么要相信这一点,也不清楚在代码中洒下notifyAll()会有什么好处,但现在是时候成为怀疑论者了。
这可能不是一直都是正确的,但在这种情况下,并没有什么不同。
很明显,这确实有区别。
是的,JVM的等待队列实现的某些方面似乎已经改变了,但这并不重要,因为您的代码总是被中断,并且完全靠运气运行。
这种情况其实很容易理解:
notifyAll()notifyAll()而唤醒,并尝试重新获取锁。哪一个会赢,还没有说明。wait(),但在第二个变体中,它将首先执行伪notifyAll()notifyAll()而醒来(B可能已经醒了,但这并不重要)并尝试重新获取锁。哪一个会赢,还没有说明。wait(),但在第二个变体中,它将首先执行伪notifyAll()notifyAll()而醒来(B可能已经醒了,但这并不重要),并尝试重新获取锁。哪一个会赢,还没有说明。正如您所看到的,使用第二个变体,您有一个可能永远运行的潜在循环,只要B永远得不到锁。使用过时的notifyAll()调用的变体依赖于错误的假设,即如果通知多个线程,正确的线程最终将收到锁。
在适合使用notifyAll()的地方使用notify()没有问题,因为所有性能良好的线程都会重新检查它们的条件,如果不满足,将再次转到wait(),因此正确的线程(或一个合格的线程)最终将取得进展。但是在等待之前调用notifyAll()并不是很好的行为,可能会导致线程永久地重新检查它们的条件,而不会让合格的线程得到它的回报。
https://stackoverflow.com/questions/55070602
复制相似问题