线程唤醒等待机制

在Java最早期的时候,常常采用suspend()和resume()方法对线程进行阻塞和唤醒,但是现在不再推荐使用了,是因为:
suspend()方法在导致线程阻塞的过程中,不会释放任何锁资源。其他线程都无法访问被它占用的锁。直到对应的线程执行resume()方法后,被挂起的线程才能继续,从而其他被阻塞在这个锁的线程才能继续执行。
如果resume操作出现在suspend操作之前,那么线程就会一直处于阻塞状态–>产生死锁,但是对于被挂起的线程,他的线程仍旧是Runnable状态

实例
class SuspendResumeTest {
    public static Object object = new Object();

    public static class TestThread extends Thread {
        public TestThread(String name) {
            super.setName(name);
        }

        @Override
        public void run() {
            synchronized (object) {
                System.out.println(getName() + "占用。。。");
                Thread.currentThread().suspend();
            }
        }
    }

    static TestThread t1 = new TestThread("我是线程1");
    static TestThread t2 = new TestThread("我是线程2");

    public static void main(String[] args) throws InterruptedException {
        t1.start();
        //Thread.sleep(200);
        t2.start();
        t1.resume();
        t2.resume();
        t1.join();
        t2.join();
    }
}
执行结果:

在这里插入图片描述
产生死锁!!!

接下来让我们来说一下,常见的,推荐的wait和notify方法进行阻塞和唤醒吧~

在Object类中,定义了wait(),notify()和notifyAll()等接口。wait方法的作用是让当前线程进入等待状态,同时wait也会让当前线程释放它所持有的锁。而notify()和notifyAll()的作用则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有线程。

  • notify:唤醒在此对象监视器上等待的单个线程
  • notifyAll:唤醒在此对象监视器上等待的所有线程
  • wait:让当前线程处于“阻塞”状态,直到其他线程调用该对象的notify方法或者notifyAll方法,当前线程被唤醒
  • wait(long timeout):让当前线程处于“阻塞”状态,直到其他线程调用该对象的notify方法或者notifyAll方法,或者超过指定的时间,当前线程被唤醒
  • wait(long timeout, int nanos):让当前线程处于“阻塞”状态,直到其他线程调用该对象的notify方法或者notifyAll方法,或者其他某个线程中断当前线程,或者已经超过某个实际时间量,当前线程被唤醒
实例
public class WaitTest {
    public static void main(String[] args) {
        ThreadA t1=new ThreadA("t1");

        synchronized (t1){
            try {
                //启动线程1
                System.out.println(Thread.currentThread().getName()+"start t1");
                t1.start();

                //主线程等待线程1通过notify唤醒
                System.out.println(Thread.currentThread().getName()+"wait()");
                t1.wait();

                System.out.println(Thread.currentThread().getName()+"continue");
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}
class ThreadA extends Thread{

    public ThreadA(String name) {
        super(name);
    }

    @Override
    public void run() {
        synchronized (this){
            System.out.println(Thread.currentThread().getName()+"call notify()");
            //唤醒当前的wait线程
            notify();
        }
    }
}

######执行结果
线程唤醒等待机制_第1张图片

结果说明

线程唤醒等待机制_第2张图片

  • 图中的主线程代表main线程,线程t1代表waitTest中启动的线程1,而锁代表t1这个对象的同步锁
  • 主线程通过new ThreadA(“t1”)新建线程“t1”,随后通过synchronized(t1)获取t1对象的同步锁,然后调用t1.start()启动线程t1
  • 主线程执行t1.wait()释放"t1对象的锁"并且进入阻塞状态,等待t1对象上的线程通过notify或者notifyAll将其唤醒
  • 线程t1运行之后,通过synchronized(this)获取当前对象的锁,接着调用notify唤醒当前对象上等待 的线程,也就是主线程
  • 线程t1运行完毕之后,释放当前对象的锁,紧接着,主线程获取t1对象的锁,然后接着运行

####扩展一下wait()–>wait(long timeout)

class ThreadA extends Thread{

    public ThreadA(String name) {
        super(name);
    }

    public void run() {
        System.out.println(Thread.currentThread().getName() + " run ");
        // 死循环,不断运行。
        while(true)
            ;
    }
}

public class WaitTimeoutTest {

    public static void main(String[] args) {

        ThreadA t1 = new ThreadA("t1");

        synchronized(t1) {
            try {
                // 启动“线程t1”
                System.out.println(Thread.currentThread().getName() + " start t1");
                t1.start();

                // 主线程等待t1通过notify()唤醒 或 notifyAll()唤醒,或超过3000ms延时;然后才被唤醒。
                System.out.println(Thread.currentThread().getName() + " call wait ");
                t1.wait(3000);

                System.out.println(Thread.currentThread().getName() + " continue");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
执行结果
main start t1
main call wait 
t1 run                  // 大约3秒之后...输出“main continue”
main continue
结果说明

和上面的wait方法类似,只不过主线程不会立刻被唤醒,而是等待3000ms后唤醒

扩展一下notify–>notifyAll

public class NotifyAllTest {

    private static Object obj = new Object();
    public static void main(String[] args) {

        ThreadA t1 = new ThreadA("t1");
        ThreadA t2 = new ThreadA("t2");
        ThreadA t3 = new ThreadA("t3");
        t1.start();
        t2.start();
        t3.start();

        try {
            System.out.println(Thread.currentThread().getName()+" sleep(3000)");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        synchronized(obj) {
            // 主线程等待唤醒。
            System.out.println(Thread.currentThread().getName()+" notifyAll()");
            obj.notifyAll();
        }
    }

    static class ThreadA extends Thread{

        public ThreadA(String name){
            super(name);
        }

        public void run() {
            synchronized (obj) {
                try {
                    // 打印输出结果
                    System.out.println(Thread.currentThread().getName() + " wait");

                    // 唤醒当前的wait线程
                    obj.wait();

                    // 打印输出结果
                    System.out.println(Thread.currentThread().getName() + " continue");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
执行结果
t1 wait
main sleep(3000)
t3 wait
t2 wait
main notifyAll()
t2 continue
t3 continue
t1 continue
结果说明
  • 主线程中新建并启动了3个线程"t1" “t2” “t3”
  • 主线程通过sleep(3000)休眠3秒,在主线程休眠3秒的过程中,我们假设"t1" “t2” “t3” 这3个线程都运行了,以"t1"为例,当它运行的时候,它会执行obj.wait()等待其他线程通过notify或者notifyAll方法来唤醒她,同样的道理,t2和t3也会等待其他线程通过notify或者notifyAll来唤醒它们
  • 主线程休眠3秒后,接着运行,执行obj.notifyAll()唤醒obj上的等待线程,即唤醒"t1" “t2” "t3"这3个线程,紧接着,主线程的synchronized(obj)运行完毕后,主动释放obj锁,这样t1 t2 t3就可以获取锁资源接着运行了。

notify() wait()等函数定义在object中,而不是Thread中的原因

Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。
wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有的“同步锁”,否则其它线程获取不到该“同步锁”而无法运行!

LockSupport提供的park和unpark方法

这两个方法是基于suspend和resume这一组合的基础上演变过来的,提供避免死锁和竞态条件

public class ThreadParkTest {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.setName("mt");
        mt.start();
        try {
            Thread.currentThread().sleep(10);
            mt.park();
            Thread.currentThread().sleep(30000);
            mt.unPark();
            Thread.currentThread().sleep(30000);
            mt.park();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    static class MyThread extends Thread {

        private boolean isPark = false;
        public void run() {
            System.out.println(" Enter Thread running.....");
            while (true) {
                if (isPark) {
                    System.out.println("Thread is Park.....");
                    LockSupport.park();
                }
            }
        }
        public void park() {
            isPark = true;
        }
        public void unPark() {
            isPark = false;
            LockSupport.unpark(this);
            System.out.println("Thread is unpark.....");
        }
    }
}

为什么使用park和unpark方法可以避免死锁的产生?

park和unpark方法控制的粒度更细,能够准确的决定线程在某个点停止
引入了许可机制,逻辑为:
1、park讲许可在等于0的时候阻塞,等于1的时候返回并将许可减为0
2、unpark尝试唤醒线程,许可加1。根据这两个逻辑,对于同一条线程,park与unpark先后操作的顺序似乎并不影响程序正确地执行,假如先执行unpark操作,许可则为1,之后再执行park操作,此时因为许可等于1直接返回往下执行,并不执行阻塞操作。

总结总结总结
suspend()、resume()已经被deprecated,不建议使用。wait与notify要保证必须有锁才能执行,而且执行notify操作释放锁后还要将当前线程扔进该对象锁的等待队列。LockSupport则完全不用考虑对象、锁、等待队列,真正解耦合线程之间的同步。

你可能感兴趣的:(>,Java多线程总结)