Java 使用锁和条件暂停/恢复执行时出现非法监视器状态异常

Java 使用锁和条件暂停/恢复执行时出现非法监视器状态异常,java,android,multithreading,asynchronous,locking,Java,Android,Multithreading,Asynchronous,Locking,我正在寻找暂停和恢复工作线程任务执行的“更好”方法 通常会使用布尔标志(如)来设置一个标志状态,工作线程在下一次迭代之前会检查该标志状态 类似于下面的代码片段 AtomicBoolean b = new AtomicBoolean(true); Runnable r = () -> { while (true) { // check for pause while (!b.get()) {

我正在寻找暂停和恢复工作线程任务执行的“更好”方法

通常会使用布尔标志(如)来设置一个标志状态,工作线程在下一次迭代之前会检查该标志状态

类似于下面的代码片段

    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();
这当然不是一个优雅的解决方案,因此我一直在尝试锁和条件

在开始尝试使用locks&conditions之前,我们先看一下示例和文档。我将使用a和a作为工作任务中暂停/恢复状态的标志

可重入锁定

使用将暂停当前线程的执行,直到可以获取锁为止,另一种方法是,如果没有被另一个线程持有,则允许我们获取锁

条件

通过找到的示例,条件允许一个线程通过使用或(或)向另一个线程发出等待或继续执行的信号,见下文

问题:

通过查看专门针对
Condition.await()
的文档以及可以调用
await()
的4个条件,我相信该条件只能用于恢复任务,而不能暂停任务

与此条件相关联的锁将自动释放,当前线程出于线程调度目的被禁用,并处于休眠状态,直到发生以下四种情况之一:

  • 其他一些线程为此条件调用signal()方法,而当前线程恰好被选为要唤醒的线程;或
这意味着我可以在工作线程中当前正在等待的条件下,从我的UI线程调用
condition.signalAll()
,从而安排它执行。i、 e.简历

但是,我看不到达到这种状态的方法,即使用条件来暂停,因为对于进入等待状态的条件,文档中说:

调用此方法时,假定当前线程持有与此条件关联的锁。由实施部门确定是否存在这种情况,如果不是,如何应对。通常,将抛出异常(例如IllegalMonitorStateException),实现必须记录该事实


我使用Android的测试示例

逻辑

  • 用户单击开始按钮
  • 执行器线程池开始递增计数器(无限期)并在UI上显示它(表示长时间运行的函数)
  • 用户单击暂停按钮
  • 执行器线程等待解锁锁(条件出现时)
  • 用户单击恢复按钮
  • 条件发出信号,执行器线程继续递增,直到停止/退出
  • 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>
    
    问题:


    使用s&s,我如何改进工作线程暂停/恢复执行的标准布尔标志检查+

    我认为对于您的逻辑(管理计数器)条件、锁等的特定示例来说,似乎有些过分

  • 如果我们将此进程视为一个长期存在的进程,那么带有显式实例化线程的旧的good 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+条件的工作方式与Object.wait()/notify()/notifyAll()完全相同(在同步块内)。当前线程持有锁时,必须调用所有条件的方法,否则会得到IllegalMonitorStateException(请参阅):

  • 我们还可以将该过程视为一系列增量任务。在这种情况下,Executor服务是一个不错的选择。只需存储对任务的引用即可在需要时取消它:

     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来通信线程(工作/作业/任务队列,生产者-消费者模式)(例如,请参阅)
    • 如果进行阻塞IO调用(例如,在套接字上),最好显式使用一个线程和close()(从另一个线程调用),如下所述。在关闭之前设置一个volatile标志isClosing,以知道刚刚发生的SocketException不是错误,而是关闭的副作用,可以忽略它
    • 如果捕获到InterruptedException,请不要忘记使用Thread.currentThread().interrupt()还原Thread.interrupted标志,如果以后有其他人可以验证()
     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 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