在线程调度程序中幕后的东西从Java8变为Java9。我正试图缩小以下计划的变化。
下面的程序产生3个并行运行并同步通过监视器锁定的线程,打印
Aa0Bb1Cc2Dd3.......Zz25
当前代码已经在所有Java版本中正常工作,我不寻求任何优化。
我在使用Object.wait()传递锁之前使用了Object.notifyAll()(这可能不是一直都是正确的,但在这种情况下它在java 1.8中没有区别)。这就是为什么这个代码版本1和版本2的两个版本。
版本1在所有Java版本(Java8和之前版本,Java 9及更高版本)中运行良好。但不是版本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;
}
几点说明:
我相信通知。
目前还不清楚为什么你相信这个或者你希望从代码中散布notifyAll()
获得什么,但现在是时候成为一个怀疑论者了。
这可能不是一直都是正确的,但在这种情况下它并没有什么不同。
好吧,显然它确实有所作为。
是的,似乎JVM的等待队列实现的某些方面已经改变,但这并不重要,因为你的代码与过时的notifyAll()
调用一直被打破,只是纯粹的运气。
这种情况实际上很容易理解:
notifyAll()
notifyAll()
而唤醒并尝试重新获取锁定。哪一个会赢,是未指明的wait()
,但在你的第二个变种中它将首先做一个虚假的notifyAll()
notifyAll()
线程A和B唤醒(B可能已经唤醒,但这无关紧要)并尝试重新获取锁定。哪一个会赢,是未指明的wait()
,但在你的第二个变种中它将首先做一个虚假的notifyAll()
notifyAll()
线程B和C唤醒(B可能已经唤醒,但这无关紧要)并尝试重新获取锁定。哪一个会赢,是未指明的正如您所看到的,使用您的第二个变体,只要B永远不会获得锁定,您就有可能永远运行的潜在循环。您的变量与过时的notifyAll()
调用依赖于错误的假设,即如果您通知多个线程,正确的线程将最终收到锁定。
在notifyAll()
适当的地方使用notify()
没有问题,因为所有表现良好的线程都会重新检查它们的条件并且如果没有完成则再次转到wait()
,因此正确的线程(或一个符合条件的线程)最终会取得进展。但是在等待之前调用notifyAll()
并不是很好,并且可能导致线程永久地重新检查它们的条件,而没有合格的线程轮到它。