【问题标题】:Java: Class entirely run in second thread / IllegalMonitorStateExceptionJava:类完全在第二个线程中运行/IllegalMonitorStateException
【发布时间】:2009-07-27 21:06:45
【问题描述】:

当你想让某个任务被另一个线程执行时,你可以扩展 Thread 或者实现 Runnable。

我尝试创建一个完全在第二个线程中运行一个类的类。

这意味着你可以调用anyMethod(),它立即返回并由第二个线程执行。

这是我的尝试:

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * Extend this class to run method calls asynchronously in the second thread implemented by this class.
 * Create method(type1 param1, type2 param2, ...) and let it call this.enqueueVoidCall("method", param1, param2, ...)
 * 
 * The thread executing the run-method will automatically call methodAsync with the specified parameters.
 * To obtain the return-value, pass an implementation of AsyncCallback to this.enqueueCall().
 * AsyncCallback.returnValue() will automatically be called upon completion of the methodAsync.
 *  
 */
public class ThreadedClass extends Thread {
    private static Object test;

    private Queue<String> queue_methods = new ConcurrentLinkedQueue<String>();
    private Queue<Object[]> queue_params = new ConcurrentLinkedQueue<Object[]>();
    private Queue<AsyncCallback<? extends Object>> queue_callback = new ConcurrentLinkedQueue<AsyncCallback<? extends Object>>();

    private volatile boolean shutdown = false;

/**
 *  The run method required by Runnable. It manages the asynchronous calls placed to this class.
 */
@Override
public final void run() {
    test = new Object();
    while (!shutdown) {
        if (!this.queue_methods.isEmpty()) {
            String crtMethod = queue_methods.poll();
            Object[] crtParamArr = queue_params.poll();
            String methodName = crtMethod + "Async";

            Method method;
            try {
                method = this.getClass().getMethod(methodName);
                try {
                    Object retVal = method.invoke(this, crtParamArr);
                    AsyncCallback<? extends Object> crtCallback = queue_callback.poll();
                    crtCallback.returnValue(retVal);
                } catch (Exception ex) {}
               } catch (SecurityException ex) {
               } catch (NoSuchMethodException ex) {}
        } else {
            try {
                synchronized(test ) {
                    test.wait();
                }
            } catch (InterruptedException ex) {
                System.out.println("READY");
            } catch (Exception ex) {
                System.out.println("READY, but " + ex.getMessage());
            }
        }
    }
}

/**
 * Asynchronously adds a method-call to the scheduler, specified by methodName with passed parameters 
 * @param methodName The name of the currently called method. methodName + "Async" is being called
 * @param parameters Parameters you may want to pass to the method
 */
protected final void enqueueVoidCall(String methodName, Object... parameters) {
    List<Object> tmpParam = new ArrayList<Object>();
    for (Object crt : parameters) {
        tmpParam.add(crt);
    }
    queue_methods.add(methodName);
    queue_params.add(parameters);
    queue_callback.add(null);
    test.notifyAll();
}

/**
 * Asynchronously adds a method-call to the scheduler, specified by methodName with passed parameters 
 * @param methodName The name of the currently called method. methodName + "Async" is being called
 * @param callBack An instance of AsyncCallback whose returnValue-method is called upon completion of the task.
 * @param parameters Parameters you may want to pass to the method
 */
protected final void enqueueCall(String methodName, AsyncCallback<? extends Object> callBack, Object... parameters) {
    List<Object> tmpParam = new ArrayList<Object>();
    for (Object crt : parameters) {
        tmpParam.add(crt);
    }
    queue_methods.add(methodName);
    queue_params.add(parameters);
    queue_callback.add(callBack);
    test.notifyAll();
}

/**
 * Complete the currently running task, optionally return values and eventually shut down. The instance of this object becomes unusable after this call. 
 */
public void shutdown() {
    shutdown=true;
}

}

现在我有两个类来测试:

public class MySecondTask extends ThreadedClass {
public void test1() {
    this.enqueueVoidCall("test1", null);
}

public void test1Async() {
    System.out.println("Start");
    try {
        // do big job here
    } catch (Exception ex) { }
    System.out.println("Done");
}
}

以及启动这些东西的主要方法:

public class TestingClass {
public static void main(String[] args) {
    MySecondTask test = new MySecondTask();
    test.start();
    System.out.println("1. Thread [1]");
    // CORRECTION, SHOULD BE:
    test.test1();
    // INSTEAD OF:
    // test.test1Async();
    for(int q=0; q<=100000; q++) {
        System.out.println("1:"+ new Date().getTime()+":"+ q);
        if ((q % 1000) == 0) {
            System.out.flush();
        }
    }
    System.err.println("1. Thread [2]");
}

}

不知何故,第二个线程的输出总是首先出现(完全),然后其余的输出到控制台上。如果线程同时运行(这是预期的结果),控制台输出应该是混合的?!

感谢任何想法以及 cmets 以改进我的编码风格。


编辑

引用的问题已经解决了。

现在我在调用的行上收到 IllegalMonitorStateException:ThreadedClass.notifyAll()。

也许我弄错了那个锁。

但是 a) 为什么需要在 wait() 周围使用 synchronized() 以及如何使 notifyAll() 调用解除阻塞 wait()?


提前致谢并致以最诚挚的问候

ps:你们都在堆栈溢出方面做得很好。你已经在不知不觉中帮助了我很多次,谢谢。继续加油!

【问题讨论】:

  • TestingClass.main() 中是否有错误?我没有看到它调用 test1() 的任何地方。
  • 等一下,你从哪里得到这个:AsyncCallback 对象,它不是 Java 的一部分

标签: java multithreading concurrency


【解决方案1】:

这意味着你可以调用 anyMethod() 立即返回 并由第二个执行 线程。

这听起来有点像使用 callables、futures 和 executors:

不想告诉你,但你可能真的想研究一下这些东西..

编辑以解决下面的评论

只需让对象中的方法如下所示:

private ExecutorService executorService = Executors.newCachedThreadPool();

public Future<SomeObject> yourMethodName(String anyArguments) {
    return executorService.submit(
        new Callable<SomeObject>() {
            public SomeObject call() {
                SomeObject obj = new SomeObject();
                /* Your Code Here*/;
                return obj;
            }
        }
    );
}

【讨论】:

  • 感谢您的提示。我已经听说过它们,我想我知道这些设施的作用。我的问题是我需要为我要调用的类的 each 方法创建另一个 Runnable 实现。还是我弄错了?我希望能够调用 myObject.method1(),也许稍后调用 myObject.method2()。这些需要立即返回并异步执行后台工作。我认为通过使用“调用队列”的尝试,可以完成这项任务......?!
  • 看看上面的新代码块。。这种类型的实现应该确实足以解决大多数问题。如果你想让它更可怕/更复杂,你可以添加反射来为你添加 Callables 和线程池提交,但我不推荐它。
  • 谢谢。在我看来,这对我来说看起来更复杂。但我会仔细看看它是否对我有帮助。
  • 我同意蒂姆的观点,除非你想进入 AOP,否则这是最好的方法。请参阅下面的答案,了解为什么您的方法在技术上正确时是不可取的。
  • 看起来既复杂又干净。也许我只是需要深入研究一下 Executors 之类的东西。有人知道关于这些具体如何工作的教程吗?我在类/接口的 javadoc 中找到了一些东西,但也许你可以推荐一些好的文档?
【解决方案2】:

您永远不会调用您的“线程”调度机制(使用队列等的机制)

我猜你试图打电话:

test.test1(); 

这反过来又将调用排队到 test1Async,但你错误地调用了:

test.test1Async();

直接在单个线程中进行整个执行。

替换:

    ....
    System.out.println("1. Thread [1]");
    test.test1Async();
    for(int q=0; q<=100000; q++)
    {
    ...

与:

   ....
    System.out.println("1. Thread [1]");
    test.test1();
    for ( int q=0; q<=100000 ; q++ ) {
    ....

关于编码风格,请在Java(和JavaScript)编码时在同一行使用左大括号。在C#中,C++和C更好。

也使用 camelCase 代替 separete_with_underscore。

这是一份包含更多关于Java's coding conventions 的文档。

【讨论】:

  • 哎呀,是的,你是对的。 (我刚刚发布的评论到哪里去了?)当 darthcoder 注释掉我看到有些事情不对的那一行时。经过其他一些小的更正后,我设法再次运行代码。但是这一次,它在我调用 notifyAll() 的行上抛出 IllegalMonitorStateException。当然,第二个线程(现在正在调用该方法)不是锁所有者,我知道。但这就是 notify 的用途,对吧?继续等待其他线程。知道如何纠正吗?谢谢
  • 来自 JDK 参考:[IllegalMonitorStateException - 如果当前线程不是此对象监视器的所有者。] 问题是调用 notifyAll() 的线程在测试时从不 synchronizes()。跨度>
  • 如果你不发布堆栈跟踪很难“猜测”,但我会猜测。在方法中: enqueueCall() 你调用 test.notifyAll();为了使用通知,所有线程必须拥有对象锁。
  • 所以,添加 synchonized( test ) { test.notifyAll(); } 看看会发生什么。顺便说一句,我对新的并发框架了解不多,但我很确定它应该习惯于通知/同步内容的这种复杂性。我知道如何使用裸锁/同步来做到这一点。如果您需要更多帮助,请告诉我。如果是这样,请考虑清理您的代码并将所有左大括号放在一行中(我很难按照它们的方式进行操作)
【解决方案3】:

我建议你完全按照蒂姆在他的回答中建议的方式来实现它。

您自己的想法虽然具有创造性,但却打破了许多使您的代码非常脆弱的最佳实践。有很多事情要记住,否则它会巧妙地破坏。

想想那个会追随你并不得不使用、维护和扩展你的代码的人。想想当你需要在一年内重新审视这段代码时会发生什么。

只是你不应该做的事情的简短列表:

  • 直接扩展线程被认为是不好的做法,最好改为实现 Runnable
  • 避免将编码方法作为文本 - 它会在第一次重构时中断
  • test1Async() 应该是私有的,否则团队的新人会直接调用它
  • 方法命名应该清楚 - *Async() 通常意味着在后台执行,而实际上是相反的方式
  • 总体脆弱性 - 假设我需要更改 test1() 以返回 int 而不是 void - 我真的会记得更改其他方法吗? 会记得一年后这样做吗?

【讨论】:

  • 感谢您的回复。好吧,实现 Runnable 而不是扩展 Thread 是我可以轻松忍受的事情......将 ...Async() 设为私有(或为可扩展性而保护)也是一个好点。现在我并不真正关心这一点,因为我只是希望代码按预期运行;o)也许它很脆弱,我同意。但是当我按照 Tim 建议的方式实现它时,我想我会再次遇到同样的问题,因为要记住更改代码时必须做的不同更改。
  • 你有一个很好的积极态度,这很酷:) 关于蒂姆的解决方案 - 优点是它非常集中。因此,例如,如果您想更改某些方法,请转到该方法并进行更改-仅此而已。无需使用另一种方法,无需在某处更新某些文本字符串 - 只需找到方法并修复它即可。现在这似乎并不重要,但几个月后你会感谢我的 :)
【解决方案4】:

你的主线程等待 test.test1Async() 返回,你在那里做什么?

【讨论】:

    【解决方案5】:

    Java 线程在不同机器上的运行方式不同。有些机器是抢占式的,有些则不是。如果第二个线程在第一个线程之前输出它的内容,那么运行代码的机器很可能是非抢占式的。如果您需要同步线程,有很多方法可以做到这一点,但如果您不关心同步,则无法保证您的线程将如何运行。

    另外,test.test1Async() 正在第一个线程中运行,而不是第二个。 (这可能是阻碍事情的原因)

    【讨论】:

    • 好的,我可以理解第一点。但它不应该在第一个线程中运行,因为当前在运行方法中的线程应该调用它(这是我的意图)。为什么从第一个线程调用它?那有什么问题...
    • 好的,今天能够读取 StackOverflow 并运行 JVM 的操作系统不是抢占式多线程的?
    • @Atmocreations - 当您告诉第二个线程启动时,它会在 run 方法中开始执行,但 test.test1Async() 不会在 run 方法中调用,而是在 main 方法中调用主线程。尝试甚至不启动第二个线程,test1Async() 仍然会运行。
    【解决方案6】:

    风格建议:看java.lang.reflect.Proxy和InvocationHandler。 你可以实现一个 InvocationHandler 来避免你正在处理的一些反射的东西,并且用户可以直接调用真正的接口方法。

    【讨论】:

      【解决方案7】:

      您正在同步调用 test1Async。如果您的 //bigjob 在这里完成,那么它将不会与您的其他线程并行运行。你想做的是这样的:

      public class TestingClass {
      public static void main(String[] args)
      {
          MySecondTask test = new MySecondTask();
          test.start();
          System.out.println("1. Thread [1]");
          //test.test1Async();
          for(int q=0; q<=100000; q++)
          {
                  System.out.println("1:"+ new Date().getTime()+":"+ q);
                  if ((q % 1000) == 0)
                  {
                          System.out.flush();
                  }
          }
          System.err.println("1. Thread [2]");
      }
      
      }
      

      请记住,您的 ThreadedClass.run() 方法将为您调用 test1.testAsynch()。我真的很惊讶你没有看到两次结果输出,或者计算混乱。

      【讨论】:

      • 明白你的意思,谢谢。现在有另一个问题......(你可能想看看我对 oscar reyes 答案的评论)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多