Java:类完全在第二个线程/ IllegalMonitorStateException中运行

时间:2009-07-27 21:06:46

标签: java multithreading concurrency

当您希望某个任务由另一个线程执行时,您可以扩展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]");
}

}

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

赞赏任何想法以及改进我的编码风格的评论。


编辑

引用的问题已经完全解决了。

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

也许我把那个锁错了。

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


提前感谢和最好的问候

p.s。:你们都在堆栈溢出方面做得很好。你已经多次帮助了我,不知道它,谢谢你。坚持下去!

7 个答案:

答案 0 :(得分:5)

  

这意味着你可以打电话   anyMethod()立即返回   由第二个执行   线程。

这听起来像是与使用callables,期货和执行者一样可疑:

讨厌打破它,但你可能真的想看看这些东西..

修改以解决以下评论

只需在对象中创建方法如下:

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;
            }
        }
    );
}

答案 1 :(得分: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++ ) {
    ....

在编码风格上,pleeeeease在与Java(和JavaScript)编码时的语句相同的行中使用左括号。在C#中,C ++和C的表现方式更好。

也可以使用camelCase而不是separete_with_underscore。

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

答案 2 :(得分:1)

我建议你完全按照蒂姆在答案中提出的方式实施它。

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

想想那个将要关注你并且必须使用,维护和扩展你的代码的人。想想当你需要在一年内重新访问这段代码时会发生什么。

只是一小段你不应该做的事情:

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

答案 3 :(得分:0)

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

答案 4 :(得分:0)

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

此外, test.test1Async()在第一个线程中运行,而不是在第二个线程中运行。 (这可能就是把事情搞砸了)

答案 5 :(得分:0)

样式建议:查看java.lang.reflect.Proxy和InvocationHandler。 你可以实现一个InvocationHandler来避免你正在处理的一些反射内容,用户可以直接调用真正的接口方法。

答案 6 :(得分:0)

您正在同步调用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()。我真的很惊讶你没有看到结果输出两次,或搞砸了计算。