不把 wait 放在同步块中,为啥这种情况不会抛出 IllegalMonitorStateException?

2020-04-23 23:59:01 +08:00
 amiwrong123

这是一个来自 Java 编程思想的例子,它只是想表达 sleep 的线程可中断,但同步 IO 等待资源,或同步获得锁失败的线程,是不可同步的。

//: concurrency/Interrupting.java
// Interrupting a blocked thread.
import java.util.concurrent.*;
import java.io.*;
import static net.mindview.util.Print.*;

class SleepBlocked implements Runnable {
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(100);
        } catch(InterruptedException e) {
            print("InterruptedException");
        }
        print("Exiting SleepBlocked.run()");
    }
}

class IOBlocked implements Runnable {
    private InputStream in;
    public IOBlocked(InputStream is) { in = is; }
    public void run() {
        try {
            print("Waiting for read():");
            in.read();
        } catch(IOException e) {
            if(Thread.currentThread().isInterrupted()) {
                print("Interrupted from blocked I/O");
            } else {
                throw new RuntimeException(e);
            }
        }
        print("Exiting IOBlocked.run()");
    }
}

class SynchronizedBlocked implements Runnable {
    public synchronized void f() {
        while(true) // Never releases lock
            Thread.yield();
    }
    public SynchronizedBlocked() {
        new Thread() {
            public void run() {
                f(); // Lock acquired by this thread
            }
        }.start();
    }
    public void run() {
        print("Trying to call f()");
        f();
        print("Exiting SynchronizedBlocked.run()");
    }
}

public class Interrupting {
    private static ExecutorService exec =
            Executors.newCachedThreadPool();
    static void test(Runnable r) throws InterruptedException{
        Future<?> f = exec.submit(r);
        TimeUnit.MILLISECONDS.sleep(100);
        print("Interrupting " + r.getClass().getName());
        f.cancel(true); // Interrupts if running
        print("Interrupt sent to " + r.getClass().getName());
    }
    public static void main(String[] args) throws Exception {
        test(new SleepBlocked());
        test(new IOBlocked(System.in));
        test(new SynchronizedBlocked());
        test(new Runnable() {  //自己写的测试线程
            @Override
            public void run () {
                System.out.println("1 start run");
                try {
                    new Object().wait();  //这句不抛异常,为什么?
                } catch (InterruptedException e) {
                    System.out.println("1 catch InterruptedException");
                    e.printStackTrace();
                }
                System.out.println("1 exit run");
            }
        });
        TimeUnit.SECONDS.sleep(3);
        print("Aborting with System.exit(0)");
        new Object().wait();  //这句抛异常
        System.exit(0); // ... since last 2 interrupts failed
    }
}

打印结果:

Interrupting SleepBlocked
Interrupt sent to SleepBlocked
InterruptedException
Exiting SleepBlocked.run()
Waiting for read():
Interrupting IOBlocked
Interrupt sent to IOBlocked
Trying to call f()
Interrupting SynchronizedBlocked
Interrupt sent to SynchronizedBlocked
1 start run
Interrupting Interrupting$1
Interrupt sent to Interrupting$1  //而且看起来,中断也没起作用?(如果有作用,之后应该出现这个线程的打印结果)
Aborting with System.exit(0)
Exception in thread "main" java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at Interrupting.main(Interrupting.java:92)

我知道 wait 正确用法是在同步方法或同步块中使用, 所以现在问题有 2 个,

  1. 为啥主函数的 new Object().wait();抛 IllegalMonitorStateException 异常,我自创的线程却没有抛出 IllegalMonitorStateException 异常呢?
  2. 从打印结果,我那个线程真的 wait 了(因为只打印出来一句)。但为啥中断它时,还是没有反应呢?
        test(new Runnable() {
            @Override
            public  void run () {
                synchronized(this) {
                    System.out.println("1 start run");
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        System.out.println("1 catch InterruptedException");
                        e.printStackTrace();
                    }
                    System.out.println("1 exit run");
                }
            }
        });

然后我试了上面这个版本的,打印结果如下:

Interrupting SleepBlocked
Interrupt sent to SleepBlocked
InterruptedException
Exiting SleepBlocked.run()
Waiting for read():
Interrupting IOBlocked
Interrupt sent to IOBlocked
Trying to call f()
Interrupting SynchronizedBlocked
Interrupt sent to SynchronizedBlocked
1 start run
Interrupting Interrupting$1
Interrupt sent to Interrupting$1
1 catch InterruptedException
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at Interrupting$1.run(Interrupting.java:75)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
1 exit run
Aborting with System.exit(0)
Exception in thread "main" java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at Interrupting.main(Interrupting.java:86)

所以,wait 之前是获得锁的情况,才可以被中断呗?

1481 次点击
所在节点    Java
4 条回复
SoloCompany
2020-04-24 01:38:43 +08:00
你自己把它扔给 Executor 执行又处理异常当然会引发异常丢失的低级错误了
SoloCompany
2020-04-24 01:39:05 +08:00
又处理异常 -> 有不处理异常
amiwrong123
2020-04-25 10:34:14 +08:00
@SoloCompany
所以我那个线程,已经产生了 IllegalMonitorStateException,但我没 catch 住呗,所以没有打印。由于有异常没 catch 住,自然我那个线程也处于死亡状态了呗
SoloCompany
2020-04-25 12:56:04 +08:00
@amiwrong123 #3 关键点在于, 只有 main thread 会提供默认的 uncaughtExceptionHandler, 也就是输出到 console, 对于程序创建出来的 thread, 管理异常是程序自己的责任

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/665512

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX