CybeX
CybeX

Reputation: 2396

IllegalMonitorStateException when using locks and conditions in Android app

I am trying to familiarize myself with Java Conditions.

Purpose:

Using the following code, I created a simple counter app that races to the stars after clicking start. You can start/stop, pause/resume the counter.

The intended purpose is (for me) to allow a function(s) to run as long as the lock is not locked. The moment the lock is locked, the incrementing should stop until resume is clicked.

Looking over this example and this SO question, I tried to implement my own version - this is not successful.

Problem:

Clicking start, the counter runs. The moment I hit pause, the lock locks correctly but an java.lang.IllegalMonitorStateException occurs when the executor runnable hits the waitCondition.await(); line.

Stack Trace:

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)

Activity Code:

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

Activity 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>

Upvotes: 1

Views: 751

Answers (1)

Nathan Hughes
Nathan Hughes

Reputation: 96385

You can’t unlock the lock and then signal using it. The lock has to be held by the thread that calls await or signal on the lock’s conditions. The api doc says:

The current thread is assumed to hold the lock associated with this Condition when this method is called.

Not having that lock held leads to the exception being thrown.

That means you can’t use the isLocked check to decide if you’re done waiting. If you look at the code example on the linked question it shows that put and take each use their own condition, and they test variables indicating the buffer’s current state in order to decide whether to wait or signal.

Upvotes: 2

Related Questions