你可以告诉我,我是多线程的新手,有点卡在这里。对于我的程序,我需要一个线程(在下面的示例中为PchangeThread
),可以在程序执行期间的任何时刻从另一个线程切换开关。
调用pixelDetectorOn()
时,线程应在启动时暂停并恢复。
除了开始/停止标志之外,两个线程很可能不需要共享任何数据。无论如何,我包括对主线程的引用,以防万一。
但是,在下面的代码中,唯一输出的消息是“在进入循环之前”,这表示线程由于某种原因从不从wait()唤醒。我猜这是一种锁定问题,但我无法弄清楚到底出了什么问题。从主线程锁定this.detector
给出了相同的结果。另外,我想知道wait()
/ notify()
范例是否真的是暂停和唤醒线程的方式。
public class PchangeThread extends Thread {
Automation _automation;
private volatile boolean threadInterrupted;
PchangeThread(Automation automation)
{
this._automation = automation;
this.threadInterrupted = true;
}
@Override
public void run()
{
while (true) {
synchronized (this) {
System.out.println("before entering loop");
while (threadInterrupted == true) {
try {
wait();
System.out.println("after wait");
} catch (InterruptedException ex) {
System.out.println("thread2: caught interrupt!");
}
}
}
process();
}
}
private void process()
{
System.out.println("thread is running!");
}
public boolean isThreadInterrupted()
{
return threadInterrupted;
}
public synchronized void resumeThread()
{
this.threadInterrupted = false;
notify();
}
}
resumeThread()
通过以下方式从主线程调用:
public synchronized void pixelDetectorOn(Context stateInformation) {
this.detector.resumeThread();
}
detector
是对PchangeThread
实例的引用。
“detector”-thread以下列方式在程序的主模块中实例化:
detector=new PchangeThread(this);
答案 0 :(得分:3)
正如您所说,您需要保护对共享标志的访问。您声明threadInterrupted
是volatile,但仍然使用同步。你只需要一个。我更喜欢使用同步,因为它使事情变得更简单。多线程很复杂,除非您知道需要更复杂的控件,否则请保持简单。这意味着无论何时读取或写入threadInterrupted
,都应同步访问。目前,您未在setThreadInterrupt()
和isThreadInterrupted()
中执行此操作。
其次,您希望在尽可能小的代码块上进行同步。在run()
内,您正在通过内循环进行同步。实际上,您只需要在threadInterrupted
的读取上进行同步。当如上所述修复isThreadInterrupted()
的实现时,您可以直接使用它并从内循环中删除synchronized块。
您正在内部循环上进行同步的事实是导致您的代码永远不会打印“线程正在运行!”的错误。 PchangeThread
获取自身锁定并调用wait()
暂停该线程。但是,此时线程仍然保持锁定。稍后,主线程调用resumeThread()
以重新启动线程。但是,该方法无法开始执行,因为它必须先等待获取锁定。但是,在通知PchangeThread
之前,它永远不会获得锁定。
您提供了两种设置threadInterrupted
的方法,但只有其中一种方法在值设置为false时通知线程。你真的需要setThreadInterrupt()
吗?我希望你不要。如果保留它,当参数为假时,它应该与resumeThread()
相同。
最后,最好是锁定私有对象而不是实例本身。您可以完全控制私有锁对象。但是,任何引用您的线程实例的人也可以将它用作同步块的锁,这可能会导致很难找到死锁。
您的代码已更改为使用我的修改:
public class PchangeThread extends Thread {
private final Object _lock = new Object();
Automation _automation;
private final boolean _threadInterrupted;
PchangeThread(Automation automation)
{
_automation = automation;
_threadInterrupted = true;
}
@Override
public void run()
{
while (true) {
System.out.println("before entering loop");
while (isThreadInterrupted()) {
try {
wait();
System.out.println("after wait");
} catch (InterruptedException ex) {
System.out.println("thread2: caught interrupt!");
}
}
process();
}
}
private void process()
{
System.out.println("thread is running!");
}
public boolean isThreadInterrupted()
{
synchronized (_lock) {
return _threadInterrupted;
}
}
public void resumeThread()
{
synchronized (_lock) {
_threadInterrupted = false;
notify();
}
}
}
答案 1 :(得分:1)
在这种情况下,我个人会问自己以下问题:是
isInterrupted
标志仅由主线程设置,例如工作线程只是读取它并决定是否等待或不基于标志但不更新它。或者它可以由主线程和工作线程设置。
如果是前者 - 请选择不稳定的布尔值。这样工作线程不会缓存volatile的值,并且总是从内存中读取它。这不会创建竞争条件,因为只有一个线程会更新它 - 主要的。将其视为发布/订阅方案。
如果您的场景属于后一类 - 请使用AtomicBoolean变量。这两种情况都比synchronized关键字更有效,因为你不会获得任何锁,但在Atomic *变量的情况下,你将使用比锁获取更轻量级的CAS操作。
答案 2 :(得分:0)
您的代码没有错(虽然不是很理想)。
我运行它,它打印所有预期的消息。可能,你只是不调用resumeThread()
。
一些建议:
不要在线程上同步,制作Runnable并在其上同步。
你想开始一些计算,但要计算的数据是什么?看起来他们是以一种单独的方式进行的。这是错误的基础。使用单通道进行数据和控制。首选方法是为此类频道使用队列。例如,LinkedBlockingQueue已经以适当的方式同步。
答案 3 :(得分:0)
我怀疑有人会读到这个,但万一有人有兴趣知道:
当我检查调试器日志时,我发现了一些奇怪的东西 - 它显示“调试停止在无法编译的源代码上:”无效;“。由于我无法想到我的源代码中可能导致此错误的任何内容,我猜测Netbeans对我使用的外部代码的某些部分有问题(它不是由断点引起的,项目编译得很好!) 。所以,我刚刚将我正在使用的第三方库更新为最新版本。并且看到:之后,当我调用resumeThread()时,我突然得到一个空指针异常。我检查了剩下的代码并快速找到了bug(实际上对线程的引用是null)。
所以,总结一下:奇怪的行为是由我的程序中的一个小错误引起的,但是外部jar中的某些东西导致了应该抛出的异常的抑制。出于好奇,我通过降级jar并“解除”bug来再次检查,然后吞下异常并且调试器退出上面提到的奇怪消息。
Netbeans版本7.1.1