簡體   English   中英

使用鎖和條件暫停/恢復執行時出現 IllegalMonitorStateException

[英]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工作任務中使用ReentrantLockCondition作為暫停/恢復狀態的標志。

可重入鎖

使用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 的測試示例

邏輯

  1. 用戶點擊開始按鈕
  2. Executor Thread Pool 開始遞增一個計數器(無限期地)並將其顯示在 UI 上(象征一個長時間運行的函數)
  3. 用戶點擊暫停按鈕
  4. 執行器線程等待解鎖(條件進入)
  5. 用戶點擊恢復按鈕
  6. 發出條件信號,執行線程繼續遞增直到停止/退出

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()以便工作線程暫停/恢復執行

我認為對於您的特定邏輯示例(管理計數器)條件、鎖等似乎是矯枉過正。

  1. 如果我們將此過程視為一個長期存在的過程,那么具有顯式實例化 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
  2. 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
  3. 我們還可以將該過程視為一系列增量任務。 在這種情況下 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

補充說明:

  • 如果您不僅需要增加計數器,還需要等待作業准備好執行,我建議使用 BlockingQueue 來通信線程(工作/作業/任務隊列,生產者-消費者模式)(參見,對於例如https://www.baeldung.com/java-blocking-queue )。
  • 如果您進行阻塞 IO 調用(例如,在套接字上),您最好顯式使用線程和 close()(從另一個線程調用),如https://stackoverflow.com/a/4426050/3319725所述。 設置一個 volatile 標志 isClosing 可以知道剛剛發生的 SocketException 不是錯誤,而只是關閉的副作用,可以忽略。
  • 如果您捕獲了 InterruptedException,請不要忘記使用 Thread.currentThread().interrupt() 恢復 Thread.interrupted 標志,前提是其他人可以稍后對其進行驗證( https://www.ibm.com/ developerworks/圖書館/j-jtp05236/index.html

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM