接上一篇文章——【线程间通信(一)—— 等待/通知机制(1)】
网址为:https://blog.csdn.net/qq_44307209/article/details/125440763)
5.当interrupt方法遇到wait方法
当线程呈wait()状态时,调用线程对象的interrupt()方法会出现InterruptedException异常。
创建项目waitInterruptException,类Service.java代码如下:
public class Service {
public void testMethod(Object lock){
try{
synchronized (lock){
System.out.println("begin wait()");
lock.wait();
System.out.println(" end wait");
}
}catch (InterruptedException e){
e.printStackTrace();
System.out.println("出现异常了,因为wait状态的线程被interrupt了!");
}
}
}
线程A代码如下:
public class ThreadA extends Thread{
private Object lock;
public ThreadA(Object lock){
super();
this.lock = lock;
}
@Override
public void run() {
Service service = new Service();
service.testMethod(lock);
}
}
运行类Run.java代码如下:
public class Run {
public static void main(String[] args) {
try{
Object lock = new Object();
ThreadA a = new ThreadA(lock);
a.start();
Thread.sleep(5000);
a.interrupt();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
运行结果如下:
begin wait()
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:503)
at Service.testMethod(Service.java:6)
at ThreadA.run(ThreadA.java:11)
出现异常了,因为wait状态的线程被interrupt了!
总结如下:
(1)执行完同步代码块就会释放对象的锁。
(2)在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放。
(3)在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放对象锁,而此线程对象会进入线程等待池中,等待被唤醒。
6.只通知一个线程
调用方法notify()一次只随机通知一个线程进行唤醒。
创建项目notify,类Service.java代码如下:
public class Service {
public void testMethod(Object lock){
try{
synchronized (lock){
System.out.println("begin wait ThreadName = "+Thread.currentThread().getName());
lock.wait();
System.out.println(" end wait() ThreadName = "+Thread.currentThread().getName());
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
再创建3个线程类ThreadA,ThreadB,ThreadC,分别以下代码:
ThreadA
public class ThreadA extends Thread {
private Object lock;
public ThreadA(Object lock){
super();
this.lock = lock;
}
@Override
public void run() {
Service service = new Service();
service.testMethod(lock);
}
}
ThreadB
public class ThreadB extends Thread {
private Object lock;
public ThreadB(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {
Service service = new Service();
service.testMethod(lock);
}
}
ThreadC
public class ThreadC extends Thread{
private Object lock;
public ThreadC(Object lock){
super();
this.lock = lock;
}
@Override
public void run() {
Service service = new Service();
service.testMethod(lock);
}
}
NotifyThread.java代码如下:
public class NotifyThread extends Thread{
private Object lock;
public NotifyThread(Object lock){
super();
this.lock = lock;
}
@Override
public void run() {
synchronized (lock){
lock.notify();
}
}
}
运行类Run.java代码如下:
public class Run {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
ThreadA a = new ThreadA(lock);
a.start();
ThreadB b = new ThreadB(lock);
b.start();
ThreadC c = new ThreadC(lock);
c.start();
Thread.sleep(1000);
NotifyThread notifyThread = new NotifyThread(lock);
notifyThread.start();
}
}
运行结果为:
begin wait ThreadName = Thread-0
begin wait ThreadName = Thread-1
begin wait ThreadName = Thread-2
end wait() ThreadName = Thread-0
可以从输出看出,调用一个方法notify()方法时,会随机将等待wait状态的一个线程进行唤醒。
而如果多次调用notify()方法时,会随机唤醒全部等待中的线程。
7.唤醒所有线程
前面的示例中通过多次调用notify()方法来实现唤醒3个线程,但并不能保证系统中仅有3个线程,也就是若notify()方法的调用次数小于线程对象的数量,会出现有部分线程对象无法被唤醒的情况。为了唤醒全部线程,可以使用notifyAll()方法。
只需将上面带代码中NotifyThread.java类中使用的notify()方法,改成notifyAll()即可。
8.方法wait(long)的使用
带一个参数的wait(long)方法的功能是等待某一时间内是否有线程对锁进行唤醒,如果超过这个时间自动唤醒。
创建项目waitHasParamMethod,其中MyRunnable.java类代码如下,实现5秒后唤醒:
public class MyRunnable {
static private Object lock = new Object();
static private Runnable runnable = new Runnable() {
@Override
public void run() {
try{
synchronized (lock){
System.out.println("wait begin time = "+System.currentTimeMillis());
lock.wait(5000);
System.out.println("wait end time = "+System.currentTimeMillis());
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
};
static private Runnable runnable1 = new Runnable() {
@Override
public void run() {
synchronized (lock){
System.out.println("notify begin time = "+System.currentTimeMillis());
lock.notifyAll();
System.out.println("notify end time = "+System.currentTimeMillis());
}
}
};
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(runnable);
t.start();
Thread.sleep(3000);
Thread t2 = new Thread(runnable1);
t2.start();
}
}
运行结果为:
wait begin time = 1656060091999
notify begin time = 1656060104999
notify end time = 1656060104999
wait end time = 1656060104999
打印日记中wait begin的时间尾数是1999,在3000毫秒后,notify begin 4999被执行,也就是在此时间点准备对呈WAITING状态的线程进行唤醒。
9.通知过早
如果通知过早,则会打乱程序的正常运行逻辑。
创建项目firstNotify,其中类MyRun.java代码如下:
public class MyRun {
private String lock = "";
private Runnable runnableA = new Runnable() {
@Override
public void run() {
try{
synchronized (lock){
System.out.println("begin wait");
lock.wait();
System.out.println(" end wait");
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
};
private Runnable runnableB = new Runnable() {
@Override
public void run() {
synchronized (lock){
System.out.println("begin notify");
lock.notify();
System.out.println(" end notify");
}
}
};
public static void main(String[] args) throws InterruptedException {
MyRun run = new MyRun();
Thread b = new Thread(run.runnableB);
b.start();
Thread.sleep(1000);
Thread a = new Thread(run.runnableA);
a.start();
}
}
运行结果如下所示:
begin notify
end notify
begin wait
方法wait永远不会被通知
如果先通知了,则wait方法也就没有必要执行了。
10.等待wait的条件发生变化
在使用wait/notify模式时,还需要注意另外一种情况,也就是wait等待的条件发生了变化,也容易造成程序逻辑的混乱。
创建waitOld项目,类Add.java代码如下:
public class Add {
private String lock;
public Add(String lock){
super();
this.lock = lock;
}
public void add(){
synchronized (lock){
ValueObject.list.add("anyString");
lock.notifyAll();
}
}
}
创建类Subtract.java代码如下:
public class Subtract {
private String lock;
public Subtract(String lock){
super();
this.lock = lock;
}
public void subtract(){
try{
synchronized (lock){
if (ValueObject.list.size() == 0){
System.out.println("wait begin ThreadName = "+Thread.currentThread().getName());
lock.wait();
System.out.println("wait end ThreadName = "+Thread.currentThread().getName());
}
ValueObject.list.remove(0);
System.out.println("list size = "+ValueObject.list.size());
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
类ValueObject.java代码如下:
import java.util.ArrayList;
import java.util.List;
public class ValueObject {
public static List list =new ArrayList();
}
两个线程类代码如下所示:
public class ThreadAdd extends Thread{
private Add p;
public ThreadAdd(Add p){
super();
this.p = p;
}
@Override
public void run() {
p.add();
}
}
public class ThreadSubtract extends Thread{
private Subtract r;
public ThreadSubtract(Subtract r){
super();
this.r = r;
}
@Override
public void run() {
r.subtract();
}
}
运行类的代码如下:
public class Run {
public static void main(String[] args) throws InterruptedException {
String lock = "";
Add add = new Add(lock);
Subtract subtract = new Subtract(lock);
ThreadSubtract thread1 = new ThreadSubtract(subtract);
thread1.setName("thread1");
thread1.start();
ThreadSubtract thread2 = new ThreadSubtract(subtract);
thread2.setName("thread2");
thread2.start();
Thread.sleep(1000);
ThreadAdd threadAdd = new ThreadAdd(add);
threadAdd.setName("threadAdd");
threadAdd.start();
}
}
运行结果如下:
wait begin ThreadName = thread1
wait begin ThreadName = thread2
wait end ThreadName = thread2
list size = 0
wait end ThreadName = thread1
Exception in thread "thread1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:657)
at java.util.ArrayList.remove(ArrayList.java:496)
at Subtract.subtract(Subtract.java:17)
at ThreadSubtract.run(ThreadSubtract.java:10)
出现这样异常的原因是因为有两个实现删除remove操作的线程,他们在Thread.sleep;之前都执行了wait方法,呈等待状态,当加操作的线程在1秒之后被运行时,通知了所有呈wait等待状态的减操作的线程,那么第一个实现减操作的线程能正确地删除list中索引为0的数据,但第二个实现减操作的线程则出现索引溢出的异常,因为list中仅仅添加了一个数据,也只能删除一个数据,所以没有第二个数据可供删除。
如何解决这样的问题呢?修改subtract方法,代码如下:
public class Subtract {
private String lock;
public Subtract(String lock){
super();
this.lock = lock;
}
public void subtract(){
try{
synchronized (lock){
while (ValueObject.list.size() == 0){
System.out.println("wait begin ThreadName = "+Thread.currentThread().getName());
lock.wait();
System.out.println("wait end ThreadName = "+Thread.currentThread().getName());
}
ValueObject.list.remove(0);
System.out.println("list size = "+ValueObject.list.size());
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
运行结果如下:
wait begin ThreadName = thread1
wait begin ThreadName = thread2
wait end ThreadName = thread2
list size = 0
wait end ThreadName = thread1
wait begin ThreadName = thread1
以上代码下载请点击该链接:https://github.com/Yarrow052/Java-package.git