V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
amiwrong123
V2EX  ›  Java

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

  •  
  •   amiwrong123 · 2020-04-23 23:59:01 +08:00 · 1537 次点击
    这是一个创建于 1676 天前的主题,其中的信息可能已经有所发展或是发生改变。

    这是一个来自 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 之前是获得锁的情况,才可以被中断呗?

    4 条回复    2020-04-25 12:56:04 +08:00
    SoloCompany
        1
    SoloCompany  
       2020-04-24 01:38:43 +08:00   ❤️ 1
    你自己把它扔给 Executor 执行又处理异常当然会引发异常丢失的低级错误了
    SoloCompany
        2
    SoloCompany  
       2020-04-24 01:39:05 +08:00
    又处理异常 -> 有不处理异常
    amiwrong123
        3
    amiwrong123  
    OP
       2020-04-25 10:34:14 +08:00
    @SoloCompany
    所以我那个线程,已经产生了 IllegalMonitorStateException,但我没 catch 住呗,所以没有打印。由于有异常没 catch 住,自然我那个线程也处于死亡状态了呗
    SoloCompany
        4
    SoloCompany  
       2020-04-25 12:56:04 +08:00
    @amiwrong123 #3 关键点在于, 只有 main thread 会提供默认的 uncaughtExceptionHandler, 也就是输出到 console, 对于程序创建出来的 thread, 管理异常是程序自己的责任
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4704 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 05:35 · PVG 13:35 · LAX 21:35 · JFK 00:35
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.