【并发编程】wait、notify、notifyAll 源码分析

前言

Github:https://github.com/yihonglei/jdk-source-code-reading(java-concurrent)

一 等待/通知机制

等待/通知机制是线程间通信的一种方式,首先需要了解wait、notify、notifyAll方法含义。

1、 wait()

wait()方法为Object对象的方法,使线程等待,进入阻塞队列。

2、notify()/notifyAll()

notify()随机通知一个线程唤醒,如果通知线程少于等待线程时,将不能及时唤醒多个线程。

notifyAll() 唤醒所有等待执行线程使其继续运行。

3、对象锁的就绪队列和阻塞队列

每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了将要获得锁的线程,

阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待cpu调度;

否则,一个线程被wait后,就会进入阻塞队列,等待下次被唤醒。

二 wait/notify

1、非wait/notify实例

在实例分析wait/notify前,先通过另外一种方式进行线程间通信。

实例类:

package com.jpeony.concurrent.waitnotify.no;

import java.util.ArrayList;
import java.util.List;

/**
 * @author yihonglei
 */
public class MyList {
    private List<String> list = new ArrayList<String>();

    public void addUserName(String username) {
        list.add(username);
    }

    public int size() {
        return list.size();
    }
}

创建线程A:

package com.jpeony.concurrent.waitnotify.no;

/**
 * @author yihonglei
 */
public class ThreadA extends Thread {
    private MyList myList;

    public ThreadA(MyList myList) {
        super();
        this.myList = myList;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                // 往集合添加元素
                myList.addUserName("element" + (i + 1));
                System.out.println(Thread.currentThread().getName() + "添加了" + myList.size() + "个元素!");
                Thread.sleep(1000);
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}

创建线程B:

package com.jpeony.concurrent.waitnotify.no;

/**
 * @author yihonglei
 */
public class ThreadB extends Thread {
    private MyList myList;

    public ThreadB(MyList myList) {
        super();
        this.myList = myList;
    }

    @Override
    public void run() {
        try {
            while (true) {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "还活着");
                if (myList.size() >= 5) {
                    System.out.println("A线程已经将list添加了5个元素,B线程要退出了");
                    // 异常法退出线程
                    throw new InterruptedException();
                }
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}

测试类:

package com.jpeony.concurrent.waitnotify.no;

/**
 * 不使用等待/通知模式,通过sleep和while配合实现线程间的通信;
 * A线程不断添加元素,B线程while循环一直检查共享的list,
 * B线程可以根据list做各种判断和处理,从而实现A线程与B线程之间的通信!
 * 即两个线程通过读写一个共享变量来完成线程通信。
 * <p>
 * 这种处理方式的缺点:
 * (1) B线程一直通过某个条件轮询,浪费cpu资源;
 * (2) B线程轮询时间大小,对轮询条件也有影响,渠道的轮询条件有可能出现"错过精准条件"
 * 比如,如果B线程判断改为myList.size() == 5,则可能出现A线程添加到5的时候,
 * B线程没有轮询到,当B线程执行到myList.size()的时候,可能取到的值为6,
 * 哪么就会出现,B线程错过了与A线程精准对接的条件,导致B线程与A线程错过了通信的机会
 *
 * @author yihonglei
 */
public class RunTest {
    public static void main(String[] args) {
        MyList myList = new MyList();

        ThreadA threadA = new ThreadA(myList);
        threadA.setName("A");
        threadA.start();

        ThreadB threadB = new ThreadB(myList);
        threadB.setName("B");
        threadB.start();
    }
}

运行结果:

 

A和B两个线程共享同一个对象,A的修改对于B可见,B线程通过条件判断退出程序。

使用这种方式处理线程通信,有以下缺点:

1)B线程一直通过某个条件轮询,浪费cpu资源;

2)B线程轮询时间大小,对轮询条件也有影响,渠道的轮询条件有可能出现"错过精准条件"

     比如,如果B线程判断改为myList.size() == 5,则可能出现A线程添加到5的时候,

     B线程没有轮询到,当B线程执行到myList.size()的时候,可能取到的值为6,

     哪么就会出现,B线程错过了与A线程精准对接的条件,导致B线程与A线程错过了通信的机会

     所以,为了解决上面这些问题,采用Object对象的wait,notify方法,通过等待/通知机制完成线程间通信。

2、wait/notify实例

创建用于发起等待的A线程:

package com.jpeony.concurrent.waitnotify.yes;

/**
 * 用于处理等待
 *
 * @author yihonglei
 */
public class ThreadA extends Thread {
    private final Object lock;

    public ThreadA(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            synchronized (lock) {
                System.out.println("wait-ThreadA begin time = " + System.currentTimeMillis());
                lock.wait();
                System.out.println("wait-ThreadA end time = " + System.currentTimeMillis());
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}

创建用于发起通知的B线程:

package com.jpeony.concurrent.waitnotify.yes;

/**
 * 用于处理通知
 *
 * @author yihonglei
 */
public class ThreadB extends Thread {
    private final Object lock;

    public ThreadB(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println("notify-ThreadB begin time = " + System.currentTimeMillis());
            lock.notify();
            System.out.println("notify-ThreadB end time = " + System.currentTimeMillis());
        }
    }
}

创建测试类:

package com.jpeony.concurrent.waitnotify.yes;

/**
 * 测试代码
 *
 * @author yihonglei
 */
public class RunTest {
    public static void main(String[] args) {
        try {
            Object lock = new Object();
            ThreadA threadA = new ThreadA(lock);
            threadA.start();

            // 5秒后调用唤醒线程,调用notify方法,唤醒等待线程
            Thread.sleep(5000);

            ThreadB threadB = new ThreadB(lock);
            threadB.start();
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}

运行结果:

线程A调用了wait方法,线程处于等待状态,5秒后线程B执行调用notify唤醒等待线程,

A继续执行完成。

三 wait/notifyAll

关于唤醒一个线程或唤醒多个线程的实例:

1. 一个用于控制线程等待的服务类

package com.jpeony.concurrent.waitnotify.notifyall;

/**
 * @author yihonglei
 */
public class MyService {
    public void serviceMethod(Object lock) {
        try {
            synchronized (lock) {
                System.out.println("begin wait ThreadName = " + Thread.currentThread().getName()
                        + " begin time = " + System.currentTimeMillis());
                lock.wait();
                System.out.println("end wait ThreadName = " + Thread.currentThread().getName()
                        + " end time = " + System.currentTimeMillis());
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}

2. 创建线程A

package com.jpeony.concurrent.waitnotify.notifyall;

/**
 * @author yihonglei
 */
public class ThreadA extends Thread {
    private Object lock;

    public ThreadA(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        MyService service = new MyService();
        service.serviceMethod(lock);
    }
}

3. 创建线程B

package com.jpeony.concurrent.waitnotify.notifyall;

/**
 * @author yihonglei
 */
public class ThreadB extends Thread {
    private Object lock;

    public ThreadB(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        MyService service = new MyService();
        service.serviceMethod(lock);
    }
}

4. 创建线程C

package com.jpeony.concurrent.waitnotify.notifyall;

/**
 * @author yihonglei
 */
public class ThreadC extends Thread {
    private Object lock;

    public ThreadC(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        MyService service = new MyService();
        service.serviceMethod(lock);
    }
}

5. 创建通知线程

package com.jpeony.concurrent.waitnotify.notifyall;

/**
 * @author yihonglei
 */
public class NotifyThread extends Thread {
    private Object lock;

    public NotifyThread(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println("begin general ThreadName = " + Thread.currentThread().getName()
                    + " begin time = " + System.currentTimeMillis());
            // 随机通知一个线程进行唤醒
            lock.notify();
            // 通知全部线程进行唤醒
            // lock.notifyAll();
            System.out.println("end general ThreadName = " + Thread.currentThread().getName()
                    + " end time = " + System.currentTimeMillis());
        }
    }
}

6. 测试类

package com.jpeony.concurrent.waitnotify.notifyall;

/**
 * wait(): 使线程等待,进入阻塞队列。
 * wait(long): 表示一个线程等待指定时间内是否有线程对锁进行唤醒,如果没有,将自动唤醒。
 * general(): 随机通知一个线程唤醒,如果通知线程少于等待线程时,将不能及时唤醒多个线程。
 * notifyAll(): 唤醒所有线程。
 *
 * @author yihonglei
 */
public class RunTest {
    public static void main(String[] args) {
        try {
            // 创建一个锁对象
            Object lock = new Object();

            ThreadA threadA = new ThreadA(lock);
            threadA.setName("A");
            threadA.start();

            ThreadB threadB = new ThreadB(lock);
            threadB.setName("B");
            threadB.start();

            ThreadC threadC = new ThreadC(lock);
            threadC.setName("C");
            threadC.start();

            // 休眠5秒后调用唤醒线程
            Thread.sleep(5000);

            NotifyThread notifyThread = new NotifyThread(lock);
            notifyThread.setName("NotifyThread");
            notifyThread.start();
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}

7. 运行结果

从运行结果可以知道,线程A,B,C处于等待,过5秒后执行通知线程,唤醒线程。

但是只是显示了A线程被唤醒,B,C线程一直处于等待状态,控制台运行按钮一直显示红色。

这是因为,通知线程调用notify只是随机唤醒一个线程,其余线程未被唤醒,所以处于等待状态。

解决这个问题的办法就是将NotifyThread中的notify方法改成notifyAll,然后再运行程序,运行结果:

从运行结果可以看出,所有线程均被唤醒。

四 总结

1、wait让线程等待执行。

2、notify只能唤醒一个线程,notifyAll唤醒多个线程。

3、等待/通知模型除了用wait/notify,还可以用ReentrantLock的Condition的await/signal实现。

ReentrantLock和Condition参考:https://blog.csdn.net/yhl_jxy/article/details/87088314

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值