![](/img/trans.png)
[英]IllegalMonitorStateException when using locks and conditions in Android app
[英]IllegalMonitorStateException when using locks and conditions to pause/resume execution
我正在尋找一種“更好”的方式來暫停和恢復工作線程任務的執行。
通常人們會使用 boolean 標志(例如AtomicBoolean)來設置標志 state,工作線程在其工作的下一次迭代之前檢查該標志。
類似於下面的代碼片段
AtomicBoolean b = new AtomicBoolean(true);
Runnable r = () -> {
while (true) {
// check for pause
while (!b.get()) {
// sleep thread then check again
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// do work
}
};
Thread p = new Thread(r);
p.start();
這當然不是一個優雅的解決方案,因此我一直在嘗試使用鎖和條件。
在開始嘗試使用鎖和條件之前,首先我們看一下示例和文檔。 我將在我的ExecutorService工作任務中使用ReentrantLock和Condition作為暫停/恢復狀態的標志。
可重入鎖
使用ReentrantLock.lock()將暫停當前線程的執行,直到可以獲取鎖為止,另一種方法是ReentrantLock.tryLock() ,它允許我們在沒有被另一個線程持有的情況下獲取鎖。
健康)狀況
通過此處找到的示例,條件允許通過使用Condition.await()或Condition.signal() (或Condition.signalAll() )向另一個線程發出信號以等待或繼續執行,見下文
問題:
通過查看專門針對Condition.await()
的文檔以及可以調用await()
的 4 個條件,我相信 Condition 只能用於恢復任務,不能暫停任務。
與此 Condition 關聯的鎖被自動釋放,並且當前線程出於線程調度目的而被禁用並處於休眠狀態,直到發生以下四種情況之一:
- 其他一些線程為此 Condition 調用 signal() 方法,當前線程恰好被選為要喚醒的線程; 或者
這意味着我可以在工作線程中當前正在await()
的條件下從我的 UI 線程調用condition.signalAll()
,從而安排它執行。 即簡歷
但是,我看不到到達此 state 的方法,即暫停使用條件,因為對於 go 的條件進入等待 state,文檔說:
調用此方法時,假定當前線程持有與此 Condition 關聯的鎖。 由實現決定是否是這種情況,如果不是,如何響應。 通常,將拋出異常(例如IllegalMonitorStateException )並且實現必須記錄該事實。
我使用 Android 的測試示例
邏輯
MainActivity.java
public class MainActivity extends AppCompatActivity {
private TextView lblCounter;
private Button btnStartStop, btnPauseResume;
private ReentrantLock pauseLock = new ReentrantLock();
private Condition waitCondition = pauseLock.newCondition();
private ExecutorService executorService = Executors.newWorkStealingPool(4);
private AtomicBoolean stopStart = new AtomicBoolean(true);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lblCounter = findViewById(R.id.counter);
btnStartStop = findViewById(R.id.startStop);
btnPauseResume = findViewById(R.id.pauseResume);
btnStartStop.setOnClickListener(v -> {
if (stopStart.get()) {
start();
} else {
stop();
}
});
btnPauseResume.setOnClickListener(v -> {
pauseResume();
});
}
public void start() {
btnStartStop.setText("Stop");
AtomicInteger i = new AtomicInteger(0);
executorService.execute(() -> {
while (true) {
// here we check if the lock is locked, if so, we should wait await until a signal is emmited
while (pauseLock.isLocked()) {
try {
waitCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int i1 = i.incrementAndGet();
lblCounter.setText(String.valueOf(i1));
}
});
}
public void stop() {
executorService.shutdownNow();
btnStartStop.setText("Start");
}
public void pauseResume() {
if (pauseLock.isLocked()) {
pauseLock.unlock();
waitCondition.signal();
btnPauseResume.setText("Pause");
} else {
pauseLock.lock();
btnPauseResume.setText("Resume");
}
}
}
主要活動 XML
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/counter"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="96dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="96dp"
android:text="123"
android:textColor="#000000"
android:textSize="36sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
<Button
android:id="@+id/startStop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="128dp"
android:text="Start"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/pauseResume"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/pauseResume"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pause"
app:layout_constraintBottom_toBottomOf="@+id/startStop"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/startStop" />
<TextView
android:id="@+id/textView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="96dp"
android:layout_marginTop="128dp"
android:layout_marginEnd="96dp"
android:text="Counter"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
上面這段代碼的一個問題是在環繞這個waitCondition.await();
之前的while
循環中。 我沒有在pauseLock
上獲得鎖,因此當用戶單擊 pauseResume 按鈕並且我在pauseResume()
中獲得鎖時,工作線程執行waitCondition.await()
導致以下異常:
E/AndroidRuntime: FATAL EXCEPTION: ForkJoinPool-1-worker-1
Process: nmu.wrpv302.myapplication, PID: 11744
java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:156)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1291)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.fullyRelease(AbstractQueuedSynchronizer.java:1752)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2064)
at com.example.myapplication.MainActivity.lambda$start$2$MainActivity(MainActivity.java:61)
^ --------------------- PROBLEM -----------------------
at com.example.myapplication.-$$Lambda$MainActivity$te94WnCx7dwprXfxnjJZuoEc1_8.run(Unknown Source:4)
at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1411)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:285)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1155)
at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1993)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1941)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
問題:
使用Condition s & Lock s,我如何改進標准 boolean 標志檢查 + thread.sleep()以便工作線程暫停/恢復執行
我認為對於您的特定邏輯示例(管理計數器)條件、鎖等似乎是矯枉過正。
如果我們將此過程視為一個長期存在的過程,那么具有顯式實例化 Thread 的舊好的 Object.wait()/notify()/notifyAll() 應該可以完美地工作:
final Object lock = new Object(); AtomicBoolean b = new AtomicBoolean(true); Runnable r = () -> { _end: while (true) { while (b.get()) { if (Thread.currentThread().isInterrupted()) { break _end; } // increment the counter } synchronized (lock) { while (.b.get()) { try { lock;wait(). } catch (InterruptedException e) { Thread.currentThread();interrupt(); break _end; } } } } }; Thread p = new Thread(r). p;start(). ... b;set(false). // disable... synchronized (lock) { b;set(true). // enable lock;notify(). // to notify for enabled state change }... b;set(false); // disable
ReentrantLock + Condition 的工作方式與 Object.wait()/notify()/notifyAll() 完全相同(在同步塊內)。 All Condition's methods must be called when the current thread holds the lock, otherwise you get IllegalMonitorStateException (see https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Condition.html ) :
final ReentrantLock lock = new ReentrantLock(); final Condition enabled = lock.newCondition(); AtomicBoolean b = new AtomicBoolean(true); Runnable r = () -> { _end: while (true) { while (b.get()) { if (Thread.currentThread().isInterrupted()) { break _end; } // increment the counter } lock.lock(); try { while (.b.get()) { try { enabled;await(). } catch (InterruptedException e) { Thread.currentThread();interrupt(); break _end. } } } finally { lock;unlock(); } } }; Thread p = new Thread(r). p;start(). ... b;set(false). // disable... lock;lock(), // if you don't lock before signal. you get IllegalMonitorStateException try { b;set(true). // enable enabled;signal(). // to notify for enabled state change } finally { lock;unlock(). }... b;set(false); // disable
我們還可以將該過程視為一系列增量任務。 在這種情況下 ExecutorService 是一個不錯的選擇。 只需存儲對您的任務的引用以在需要時取消它:
ExecutorService executor = Executors.newSingleThreadExecutor(); // one single thread - no race conditions between sequentially submitted tasks guaranteed Future task; task = executor.submit(() -> { // start increment while (.Thread.currentThread();isInterrupted()) { // increment the counter } }). ... task;cancel(true). // stop increment... task = executor.submit(() -> { // start increment again while (.Thread;currentThread().isInterrupted()) { // increment the counter } }). ..; task.cancel(true); // stop increment
補充說明:
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.