Java的阻塞和中断机制( wait notify使用 wait和sleep区别 interrupt使用和其他中断方法)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/alcoholdi/article/details/79475029


wait、notify和notifyAll

wait和notify(notifyAll)一般是成对搭配出现的,用来资源调控。wait用来将当然线程挂起,notify/notifyAll用来恢复线程。它是类Object的方法,也就是所有的对象都可以使用。一个简单的例子

public class WaitClassDemo {

    private static SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");

    public static void main(String[] args) {
        Object obj = new Object();
        new AThread(obj).start();
        new BThread(obj).start();
    }

    static class AThread extends Thread {

        Object obj;

        public AThread(Object obj) {
            setName("AThread");
            this.obj = obj;
        }

        @Override
        public void run() {
            synchronized (obj) {
                System.out.println(sdf.format(new Date()) + " AThread before wait()");
                try {
                    obj.wait(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(sdf.format(new Date()) + " AThread after wait()");
            }
        }
    }

    static class BThread extends Thread {

        Object obj;

        public BThread(Object obj) {
            setName("BThread");
            this.obj = obj;
        }

        @Override
        public void run() {
            synchronized (obj) {
                System.out.println(sdf.format(new Date()) + " BThread before notify()");
                obj.notify();
                try {
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(sdf.format(new Date()) + " BThread after notify()");
            }
        }
    }
}
//打印
//14:22:34 AThread before wait()
//14:22:34 BThread before notify()
//14:22:39 BThread after notify()
//14:22:39 AThread after wait()

1、wait/notify是需要需要获取对象锁的,也就是需要写在同步代码块或同步方法内部,可以理解为用synchronize包裹的。如果不用,编译会通过,但运行时会抛出java.lang.IllegalMonitorStateException。

2、wait/notify是针对某个对象,是类Object的方法,并且注意要保证synchronize、waite和notify3者都是针对同一个具体对象。比如上面的synchronize锁的是obj这个对象,wait和notify也是由的obj对象。

3、上面这个中wait()执行后该线程就处于阻塞阶段,并且把当前的锁给释放了。BThread得以继续。notify调用后,会立即轮转到wait()方法那吗?答案是不会,上面的例子显示,notify()需要把这个代码块的Thread.sleep(5000L)执行完,退出代码块后才轮转到wait()方法那。也很合理,毕竟同一时间里,只有一个线程能拿到锁执行synchronize包裹的代码里。



wait方法也有带参数版的,wait(long timeout)和wait(long timeout, int nanos),后者看了下源码,只是判断如果如果nanos>0,让timeout++。看来虚拟机时间还是没精确到纳秒的地步。

带参数的wait方法意思是等过了timeout毫秒后,就会获得该锁。

但是如果此时锁在别的线程那里,wait()处于的AThread是不能往下执行,下面例子中如果把BThread的注释打开,就是要等BThread走出synchronize块后才可以。

public class WaitClassDemo {

    private static SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");

    public static void main(String[] args) {
        Object obj = new Object();
        new AThread(obj).start();
        //new BThread(obj).start();
    }

    static class AThread extends Thread {

        Object obj;

        public AThread(Object obj) {
            setName("AThread");
            this.obj = obj;
        }

        @Override
        public void run() {
            synchronized (obj) {
                System.out.println(sdf.format(new Date()) + " AThread before wait()");
                try {
                    obj.wait(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(sdf.format(new Date()) + " AThread after wait()");
            }
        }
    }

    static class BThread extends Thread {

        Object obj;

        public BThread(Object obj) {
            setName("BThread");
            this.obj = obj;
        }

        @Override
        public void run() {
            synchronized (obj) {
                System.out.println(sdf.format(new Date()) + " BThread before");
                try {
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(sdf.format(new Date()) + " BThread after");
            }
        }
    }
}
//注释BThread
//19:03:43 AThread before wait()
//19:03:44 AThread after wait()
//不注释BThread
//19:14:13 AThread before wait()
//19:14:13 BThread before
//19:14:18 BThread after
//19:14:18 AThread after wait()


扫描二维码关注公众号,回复: 3027860 查看本文章


调用notify()会恢复第一个执行wait()的线程。其他的不动。

调用notifyAll()会按照后进先出(LIFO)的原则恢复线程。

public class WaitClassDemo {

    private static SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");

    public static void main(String[] args) {
        Object obj = new Object();
        for (int i = 0; i < 3; i++) {
            new AThread(i + "", obj).start();
        }
        new BThread(obj).start();
    }

    static class AThread extends Thread {

        Object obj;

        public AThread(String name, Object obj) {
            setName("AThread" + name);
            this.obj = obj;
        }

        @Override
        public void run() {
            synchronized (obj) {
                System.out.println(sdf.format(new Date()) + " " + getName() + " before wait()");
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(sdf.format(new Date()) + " " + getName() + " after wait()");
            }
        }
    }

    static class BThread extends Thread {

        Object obj;

        public BThread(Object obj) {
            setName("BThread");
            this.obj = obj;
        }

        @Override
        public void run() {
            synchronized (obj) {
                System.out.println(sdf.format(new Date()) + " BThread before notify()");
                obj.notify();
//                obj.notifyAll();
                try {
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(sdf.format(new Date()) + " BThread after notify()");
            }
        }
    }
}

//调用notify
//19:35:51 AThread0 before wait()
//19:35:51 AThread1 before wait()
//19:35:51 AThread2 before wait()
//19:35:51 BThread before notify()
//19:35:56 BThread after notify()
//19:35:56 AThread0 after wait()
//调用notifyAll
//19:39:51 AThread0 before wait()
//19:39:51 AThread2 before wait()
//19:39:51 AThread1 before wait()
//19:39:51 BThread before notify()
//19:39:56 BThread after notify()
//19:39:56 AThread1 after wait()
//19:39:56 AThread2 after wait()
//19:39:56 AThread0 after wait()

下面是一个利用wait和notifyAll实现的生产者消费者队列。因为即使notifyAll调用了,也需要退出synchronize代码才会真正去唤醒另一个线程,所以notifyAll可以写在Queue的操作之前。

public class MainClass {

    public static void main(String[] args) {
        QueueBuffer q = new QueueBuffer(2);
        for(int i=0; i<5; i++) {
            Producer p = new Producer(q);
            p.start();
        }
        for(int i=0; i<2; i++) {
            Consumer c = new Consumer(q);
            c.start();
        }
    }

    static class QueueBuffer{
        Queue<Integer> queue = new LinkedList<>();
        int size;
        AtomicInteger seq = new AtomicInteger();

        public QueueBuffer(int size) {
            this.size = size;
        }

        public synchronized void put() {
            while (queue.size() == size) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            notifyAll();
            int num = seq.getAndIncrement();
            queue.offer(num);
            System.out.println("producer --- " + num);
        }

        public synchronized int get() {
            while (queue.size() == 0) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            notifyAll();
            return queue.poll();
        }
    }


    static class Producer extends Thread{

        QueueBuffer q;
        static AtomicInteger seq = new AtomicInteger();

        public Producer(QueueBuffer q) {
            this.q = q;
        }

        @Override
        public void run() {
            while (true) {
                q.put();
            }
        }
    }

    static class Consumer extends Thread{

        QueueBuffer q;

        public Consumer(QueueBuffer q) {
            this.q = q;
        }

        @Override
        public void run() {
            while (true) {
                int num = q.get();
                try {
                    sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("consumer --- " + num);
            }
        }
    }
}



sleep

Thread.sleep(long)应该是我们最常用的,一般也知道sleep方法不会释放锁(如果写在synchronize里的话)。

所以跟wait的区别是

1、sleep是Thread类的方法,是「静态方法」。wait是Object类的方法,调用需要具体的对象。

2、sleep是不释放锁的,解除方法要么是timeout,或者interrupt一下让它抛出InterruptedException。wait是释放锁的,可以被notify/notifyAll恢复,同样也可以timeout或者interrupt。

3、sleep在哪里都可以调用,wait必须在同步方法或同步块里调用,并且同步的对象要跟wait的对象一样。

4、sleep作用只是线程的操作,用于短时间暂停线程,wait/notify可以用作线程间通信,达到资源调度的功能。



yield

yield方法也是Thread类的静态方法,会把当前线程从可运行状态变成就绪状态,之后会cpu会从众多就绪状态的线程中选择一个来执行。选线程是根据线程优先级顺序的,如果没有比当前线程更高优先级的就绪线程,完全有可能选回刚才执行yield方法的线程。


join

join也是Thread类方法,非静态,表示等待该线程结束,当前线程才继续执行。

public class JoinClassDemo {

    private static SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");

    public static void main(String[] args) {

        System.out.println(sdf.format(new Date()) + " MainThread entry");

        JoinThread t = new JoinThread();
        t.start();
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(sdf.format(new Date()) + " MainThread exit");

    }

    static class JoinThread extends Thread {

        @Override
        public void run() {
            System.out.println(sdf.format(new Date()) + " JoinThread entry");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(sdf.format(new Date()) + " JoinThread exit");
        }
    }
}

//17:24:18 MainThread entry
//17:24:18 JoinThread entry
//17:24:23 JoinThread exit
//17:24:23 MainThread exit



线程中断

首先Thread有两个一个暂停方法suspend()和一个停止方法stop()。两个都已经已经@deprecated废弃了。suspend()暂停和resume()继续容易造成死锁,stop()具有固有的不安全性。具体可以看Java API的文档注释。

所以抛弃上面的方法后,一般我们会用以下几个方法退出线程。

1.设计标记位法

public class InterruptDemo {

    private static SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");

    public static void main(String[] args) {
        new InterruptThread().start();
    }

    static class InterruptThread extends Thread {

        public boolean stopFlag = true;

        @Override
        public void run() {
            while (stopFlag) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(sdf.format(new Date()));
            }
        }
    }
}

一个变量作为标记位,判断标记位以确定退出循环达到退出线程。

缺点就是如果代码并没有这种循环语句,或者线程被其他语句阻塞了,线程可能一直不会去检查标记位。

2.interrupt中断

public class Thread implements Runnable {

    //中断目标线程
    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }

    //返回目标线程的中断状态	static在这里理解为:只有当前线程才能中断自己,不允许别的线程中断自己
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

    //判断目标线程是否中断
    public boolean isInterrupted() {
        return isInterrupted(false);
    }

    private native boolean isInterrupted(boolean ClearInterrupted);

}

线程里有一个boolean类型的中断状态,是一个标记位,是存在Native层的。当使用Thread的interrupt()方法时,线程的中断状态会被设置为true。一些阻塞方法就会抛出一个异常InterruptedException。如果没有这种阻塞方法?那就什么都不会做。下面是两种标准用法

public class InterruptDemo {

    private static SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss:SSS");

    public static void main(String[] args) {
        InterruptThread t = new InterruptThread();
//        Interrupt2Thread t = new Interrupt2Thread();
        t.start();
        try {
            Thread.sleep(3500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.interrupt();
    }

    static class InterruptThread extends Thread {

        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    //抛出InterruptedException后中断标志被清除
                    System.out.println(sdf.format(new Date()) + " catch " + isInterrupted());
                    return;
                }
                System.out.println(sdf.format(new Date()) + " " + isInterrupted());
            }
        }
    }

    static class Interrupt2Thread extends Thread {

        @Override
        public void run() {
            while (!isInterrupted()) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    //抛出InterruptedException后中断标志被清除
                    //可以再次调用interrupt恢复中断
                    System.out.println(sdf.format(new Date()) + " catch " + isInterrupted());
                    interrupt();
                }
                System.out.println(sdf.format(new Date()) + " " + isInterrupted());
            }
        }
    }
}
//15:08:01:249 false
//15:08:02:251 false
//15:08:03:252 false
//15:08:03:749 catch false

InterruptThread是在catch中直接return结束线程。Interrupt2Thread是catch中再次调用interrupt恢复中断状态,下次判断isInterrupted()中结束线程。

需要注意的点如下

①、线程不应该交给别的线程中断,应该由自己中断自己,过程中保证资源和变量已合理的处理了(该关的关,该释放的释放)。

②、所谓的interrupt线程中断,只是修改了一个标记位,需要我们判断标记位做后续的处理。如果catch代码块什么都不处理,会继续跑完剩下的代码。所以应该理解为『并不是中断,而是通知你应该自行中断了』

③、注意在Thread.sleep这些方法,抛出InterruptedException异常后会清除标记位状态。下图为文档说明


类似的方法有

Thread.sleep

Thread.join

Object.wait

BlockingQueue.put(e)和take()   这可以用于实现生产者消费者队列


3.使用FutureTask.cancel(true)或者使用线程池的shutdown()方法(比如ThreadPoolExecutor.shutdown)

AsyncTask源码解析 从AsyncTask讲到线程池》中讲到了java1.5的java.util.concurrent包带来新的线程处理方式。比如说FutureTask和ExecutorService。

看FutureTask.cancel源码可以知道,所谓的cancel(true),内部也只是调用了interrupt()

public class FutureTask<V> implements RunnableFuture<V> 
	...
    public boolean cancel(boolean mayInterruptIfRunning) {
        if (!(state == NEW &&
              UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
                  mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
            return false;
        try {    // in case call to interrupt throws exception
            if (mayInterruptIfRunning) {
                try {
                    Thread t = runner;
                    if (t != null)
                        t.interrupt();
                } finally { // final state
                    UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
                }
            }
        } finally {
            finishCompletion();
        }
        return true;
    }
    ...
}

ThreadPoolExecutor.shutdown方法也是一样

public class ThreadPoolExecutor extends AbstractExecutorService {
	...
    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(SHUTDOWN);
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

    private void interruptIdleWorkers() {
        interruptIdleWorkers(false);
    }

    private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }
    ...
}





猜你喜欢

转载自blog.csdn.net/alcoholdi/article/details/79475029
今日推荐