Java线程 - 非法监视器状态异常

时间:2016-02-23 04:20:23

标签: java multithreading

我正在尝试实现线程,其中一个线程生成一个随机数,而另一个线程在生成随机数时等待它应该通知并等待另一个线程执行相同操作。我得到非法监控状态异常,请帮帮我指出我的错误

class Dice
{
    int diceValue;

    public Dice()
    {
        this.diceValue=0;
    }
}
public class DiceGame  implements Runnable
{
    Dice d;

public DiceGame()
{
    this.d=new Dice();
}
public void run()
{
    if(Thread.currentThread().getName().equals("Player 1"))
    {
        Random rg=new Random();
        for(int i=0;i<6;i++)
        {
            synchronized(d)
            {

                d.diceValue=rg.nextInt(6);
                System.out.println(Thread.currentThread().getName()+" dice Value is "+d.diceValue);
                d.notifyAll();
                try 
                {
                    d.wait();
                }

                catch (InterruptedException e) 
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
    else if(Thread.currentThread().getName().equals("Player 2"))
    {
        Random rg=new Random();
        for(int i=0;i<6;i++)
        {
            synchronized(d)
            {
                try 
                {
                    d.wait();
                } 
                catch (InterruptedException e) 
                {
                    e.printStackTrace();
                }
                d.diceValue=rg.nextInt(6);
                System.out.println(Thread.currentThread().getName()+"dice Value is ");
                d.notifyAll();
            }
        }
    }
}



public static void main(String []args)
{
    DiceGame dg=new DiceGame();

    Thread tr1=new Thread(dg);
    Thread tr2=new Thread(dg);

    tr1.setName("Player 1");
    tr2.setName("Player 2");

    tr1.start();
    tr2.start();
}
}

3 个答案:

答案 0 :(得分:4)

        synchronized(d)
        {
            try 
            {
                d.wait();

每当您看到对wait的无条件通话时,您就知道那里有一个错误。在你wait之前,你必须确保你等待的事情还没有发生。这就是你输入synchronized块的原因,对吧?

wait / notify机制的重点是你可以自动释放锁并等待通知。如果您在致电wait之前未检查谓词(您正在等待的事情),那么这可能会有效。

  

这里需要同步块来在调用wait时保持监视器。

是的,因为除非您在同步区块内,否则您无法判断您正在等待的事情是否已经发生。由于您必须在等待之前检查它是否已经发生,因此您只能在wait块内调用synchronized。但你没有检查!你理解了这个要求,但不理解它的基本原理,所以你正式遇到它,但仍然设法创造了要求旨在防止的问题!

答案 1 :(得分:0)

我猜,问题是你在等待自己之前通知其他所有线程。

 d.notifyAll();
 try 
 {
    d.wait();
 }

请参阅此帖:https://stackoverflow.com/a/828341/5602214

答案 2 :(得分:-1)

您的代码可以通过多种方式进行改进,但只需要一些小工具即可:

class Dice
{
    int diceValue;

    public Dice()
    {
        this.diceValue=0;
    }
}
public class DiceGame  implements Runnable
{
    Dice d;

    public DiceGame()
    {
        this.d=new Dice();
    }
    @Override
    public void run()
    {
        if(Thread.currentThread().getName().equals("Player 1"))
        {
            final Random rg=new Random();
            for(int i=0;i<6;i++)
            {
                synchronized(d)
                {

                    d.diceValue=rg.nextInt(6);
                    System.out.println(Thread.currentThread().getName()+" dice Value is "+d.diceValue);
                    d.notifyAll();

                    try
                    {
                        d.wait();
                    }

                    catch (final InterruptedException e)
                    {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        }
        else if(Thread.currentThread().getName().equals("Player 2"))
        {
            final Random rg=new Random();
            for(int i=0;i<6;i++)
            {
                synchronized(d)
                {
                    try
                    {
                        d.wait();
                    }
                    catch (final InterruptedException e)
                    {
                        e.printStackTrace();
                    }

                    d.diceValue=rg.nextInt(6);

                    System.out.println(Thread.currentThread().getName()+" dice Value is "+d.diceValue);
                    d.notifyAll();
                }
            }
        }
    }



    public static void main(final String []args) throws InterruptedException
    {
        final DiceGame dg=new DiceGame();

        final Thread tr1=new Thread(dg);
        final Thread tr2=new Thread(dg);

        tr1.setName("Player 1");
        tr2.setName("Player 2");

        tr2.start();
        Thread.sleep(100);
        tr1.start();
    }
}

我没有非法的控制器异常,但第一个线程永远被锁定。基本上问题是第一个线程掷骰子,然后在之前调用d.notifyAll 实际上第二个线程可以启动并等待骰子。通过首先启动线程2然后等待一点并启动线程1来天真地解决这个问题。

您可能还会考虑:

  • 使用Java约定{}
  • rg.nextInt给出介于0..5之间的值,而不是1..6
  • 根据线程的名称,使线程代码的工作方式不同是个坏主意。在OOP中,不同的行为用后代类表示。

我猜你们希望通过多个玩家来推动骰子的通用解决方案。这个问题不一定是需要并发编程的问题,因为掷骰子在玩家之间连续进行。您当然可以将玩家作为线程,但在任何时间点只有一个线程处于活动状态。在使用Threads的情况下,您应该实现自己的调度逻辑,以确保一个接一个的线程调度。使用监视器(例如synchornize(d))不提供任何排序​​保证,它仅用于保证最多一个线程可以在任何时间点访问骰子。

具有任意数量的播放器但没有线程的解决方案(毕竟这不是并发问题)显示了这种行为:

import java.util.Random;

class Dice {
    private final Random rg=new Random();
    private int diceValue=1;

    public void roll() {
        diceValue=rg.nextInt(6)+1;
    }

    @Override
    public String toString() {
        return "value="+diceValue;
    }
}

public class Player extends Thread {
    Dice dice;
    int rollsLeft=6;

    public Player(final Dice dice) {
        this.dice=dice;
    }

    @Override
    public void run() {
        while (rollsLeft>0) {
            synchronized(dice) {
                // dice obtained
                dice.roll();
                System.out.println(Thread.currentThread().getName()+" rolled "+dice);
            }
            // dice released
            rollsLeft--;

            // just wait a little to make it slower and let other threads to join 
            try {
                Thread.sleep(100);
            } catch (final InterruptedException e) {
                // ignore
            }
        }
    }

    public static void main(final String []args) throws InterruptedException {
        final Dice dice=new Dice();
        final Player player1=new Player(dice);
        final Player player2=new Player(dice);

        player1.start();
        player2.start();
    }
}

给出了:

Thread-0 rolled value=1
Thread-1 rolled value=6
Thread-0 rolled value=2
Thread-0 rolled value=4
Thread-1 rolled value=2
etc...

如您所见,订单(即player1,player2,player1,player2)无法保证。

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

class Dice {
    private final Random rg=new Random();
    private int diceValue=1;

    public void roll() {
        diceValue=rg.nextInt(6)+1;
    }

    @Override
    public String toString() {
        return "value="+diceValue;
    }
}

public class Player {
    Dice dice;
    String player;

    public Player(final Dice dice,final String player) {
        this.dice=dice;
        this.player=player;
    }

    public void roll() {
        dice.roll();
        System.out.println(player+" rolled "+dice);
    }

    public static void main(final String []args) throws InterruptedException {
        final Dice dice=new Dice();
        final List<Player> players=new ArrayList<Player>();
        players.add(new Player(dice,"Ann"));
        players.add(new Player(dice,"Ben"));
        players.add(new Player(dice,"Cecil"));
        players.add(new Player(dice,"Denise"));

        for (int rounds=0;rounds<6;rounds++) {
            System.out.println("---");
            for (final Player player:players) {
                player.roll();
            }
        }
    }
}

这给你预期的输出,即Ann,Ben,Cecil,Denise有6轮掷骰子。