Java多线程中的同步问题

3
我只学过wait()notify()notifyAll(),所以我必须使用这些函数来控制程序。
该程序有多个线程,每个线程单独打印一组消息,但我不能在特定线程运行时阻塞其他线程。
我找到了很多方法,但我完全不理解。我的代码如下。 LeftRightCar.java:
public synchronized void run(){
    try{
        while(lock) wait();
        lock=true;
        System.out.println("LeftSideCar: "+left);
        state="Left";
        Direction();
        System.out.println("RightSideCar: "+right);
        state="Right";
        Direction();
        notifyAll();
        lock=false;
    }catch(InterruptedException e){
        e.printStackTrace();
    }
}

FrontCar.java:

public synchronized void run(){
    try{
        while(lock) wait();
        lock=true;
        System.out.println("frontCarVelocity: "+frontCarSpeed+": km/h");
        Direction();
        notifyAll();
        lock=false;
    }catch(InterruptedException e){
        e.printStackTrace();
    }
}

我正在使用lock布尔变量来使其他线程在特定线程执行期间等待。 我真的希望得到正常的结果,即不混杂消息。 但我的结果是这样的。

左侧汽车:接近...

前车速度:40: 公里/小时 方向:不要踩左侧车道...

方向: 右侧汽车:接近... 降低速度... 方向

: 不要踩右侧车道... 80 → 70 → 60 → 50 → 40

我如何只使用wait()notify()notifyAll()来解决问题?上述两个类是Sensor.java的子类,该类具有lock布尔变量。
+一些特定的信息以便理解
public class AutonomousCar {
public static void main(String[] args){
    FrontCar f=new FrontCar();
    LeftRightCar lr=new LeftRightCar();
    Thread Car[]={new Thread(f),new Thread(lr)};
    Car[0].start();
    Car[1].start();
}

AutonomousCar类表示具有可变传感器的汽车。 FrontCar类是感应前车速度的传感器,LeftRightCar类是感应左右车辆移动的传感器。

由于它是实时系统,因此可以通过Java中的线程表示并发执行。

此外,Direction函数是一个重载函数,如下所示。

LeftRightCar.java的方向

public void Direction(){
    System.out.print("Direction : ");
    if(state.equals("Left")){
        if(left.equals("None")) System.out.println(message.get("None"));
        else if(left.equals("Approaching...")) System.out.println(message.get("Approaching-L"));
        else if(left.equals("Too close...")){
            System.out.println(message.get("Close-L"));
            isLeftClose=true;
        }
    }
    else if(state.equals("Right")){
        if(right.equals("None")) System.out.println(message.get("None"));
        else if(right.equals("Approaching...")) System.out.println(message.get("Approaching-R"));
        else if(right.equals("Too close...")){
            if(!isLeftClose) System.out.println(message.get("Close-R"));
            else System.out.println(message.get("Close-LR"));
        }
    }
}

FrontCar.java的方向

public void Direction(){
    System.out.print("Direction : ");
    if(frontCarSpeed==100){
        System.out.println(message.get("S"));
    }
    else if(speed<=frontCarSpeed){
        System.out.print(message.get("I"));
        while(speed<frontCarSpeed){
            System.out.print(speed+" → ");
            speed+=10;
        }
        System.out.println(speed);
    }
    else if(speed>frontCarSpeed){
        System.out.print(message.get("D"));
        while(speed>frontCarSpeed){
            System.out.print(speed+" → ");
            speed-=10;
        }
        System.out.println(speed);
    }
}

2
请创建一个 [MCVE]。否则我们无法帮助您。就目前而言,我看不出这两个“Car”如何相互通信,它们在不同的对象上进行“同步”。 - Turing85
2
引用块(以“>”为前缀的内容)不是为了强调或突出显示内容,而是用于引用外部来源的内容,或在某些情况下用于程序输出。 - Mark Rotteveel
LeftRightCar和FrontCar是线程对象吗? - Amber Beriwal
@AmberBeriwal 是的,它们实现了 Runnable 接口。 - molamola
@haram,你能否请发一下 LeftRightCarFrontCar 的完整代码? - xingbin
我有一种感觉,你对synchronized存在一个重大误解。就目前而言,我看不到简单的方法来修复你的程序,除非重新编写大部分代码。也许你应该退后一步,重新学习一下教程,例如这个 - Turing85
2个回答

1

这段代码存在一个非常基本的缺陷,与wait()和notify()方法的实现有关。在继续解决方案之前,让我们看看这些方法实际上是如何工作的。

理解wait()和notify(): wait()notify()都是对象级别的API。它适用于当前正在使用该对象的线程,因此等待列表是在每个对象级别而不是线程级别上维护的。请考虑以下示例:

public class Example{
  int counter = 0;

  synchronized void printCounter() throws Exception{
    notify();
    System.out.println(Thread.currentThread().getName() + "-->" + (counter++));
    wait();
  }

  public static void main(String[] args){
      Example example = new Example();
      Thread t1 = new MyThread(example);
      t1.setName("MyThread-1");
      Thread t2 = new MyThread(example);
      t2.setName("MyThread-2");
      t1.start();
      t2.start();
  }
}

class MyThread extends Thread{
    private Example obj = null;

    MyThread(Example obj){
        this.obj = obj;
    }

    public void run(){
        while(true){
            try{
                obj.printCounter();
                Thread.sleep(200);
            }catch(Exception ex){

            }
        }
    }
}

This example有两个线程,t1和t2,一个对象example。我们已经为example对象调用了wait()和notify()方法,因此如果t1执行wait()语句,则会将其添加到对象example的等待列表中。其他线程也是如此。
现有代码存在问题:需要进行多项改进。
  1. 在run方法上同步:不需要同步,因为每个线程都有自己的run实现,而且线程对象不会被共享。
  2. 布尔变量锁:直接使用布尔变量来维护同步不是一个好的选择,因为当多个线程同时处理时可能存在一致性问题。
  3. wait()方法:这就是实际问题所在。我们有两个对象,一个是LeftRightCar,另一个是FrontCar。因此,当调用LeftRightCar的wait()方法时,线程将被添加到LeftRightCar对象的等待队列中。当调用FrontCar的wait()方法时,线程将被添加到FrontCar对象的等待队列中。因此,在实际情况下,对不同对象进行wait()notify()调用是无法帮助的。

解决方案:

应该在某个公共对象上调用wait()notify(),而不是这些类的对象。因此,以下代码应该可以工作:

//lock is an instance of Object now, NOT a boolean variable
public void run(){ 
  synchronized(lock){
    lock.notifyAll();
    ...
    ...
    lock.wait();
  }
}

2
这也会导致死锁。两个线程都调用了 wait(),没有人调用 notify() / notifyAll() - Turing85
@Turing85 谢谢你的指出。我错过了它,因为我更专注于解释 wait() 调用中的缺陷。已更新答案。 - Amber Beriwal
@Turing85,我们会把之前的错误称为死锁吗?因为线程没有锁定任何资源,就像每个线程都在等待。不知道该怎么称呼它... - Amber Beriwal
从技术上讲,你是正确的。从技术上讲,这完全相反于死锁,因为事情得到了解锁。从技术上讲,死锁需要至少两个由至少两个线程共享的锁。但总体症状是相同的:程序无法继续进行。 - Turing85
1
@haram 如果您不想维护它们的执行顺序,那么这样做是可以的。通常它们用于维护顺序。 - Amber Beriwal
显示剩余2条评论

0
我正在使用锁布尔变量来使其他线程在特定线程执行期间等待。
如果您的锁由(“this”)保护:
您的方法无论如何都是同步的,因此一次只有一个线程有机会执行该代码块。

被“this”保护的意思是什么? - molamola
这指的是当前对象。 - Yati Sawhney
我知道 "this" 的意思,但它没有同步。这就是我的问题所在。消息混乱。 - molamola
https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html - Yati Sawhney
你需要理解同步的概念,但在你的情况下这并不重要,因为你没有使用共享实例。此外,我没有发现任何使用布尔变量锁的地方。wait()和notify()需要在同步上下文中调用,而你已经在方法上放置了synchronized关键字。 - Yati Sawhney

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接