Java使用wait/notify实现线程间通信上篇

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

Java使用wait/notify实现线程间通信上篇

爱吃南瓜糕的北络   2022-12-12 我要评论

线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体。线程间的通信就是成为整体的必用方案之一,使用线程间进行通信后,系统之间的交互性会更强大,大大提高CPU利用率。

等待/通知机制

(1)不使用等待/通知机制实现线程间通信

样例代码如下:

public class TestC2 {
    public static void main(String[] args) throws Exception {
        MyList myList = new MyList();
        ThreadA threadA = new ThreadA(myList);
        threadA.setName("A");
        ThreadB threadB = new ThreadB(myList);
        threadB.setName("B");
        threadB.start();
        threadA.start();
    }
}
class MyList {
    volatile private List list = new ArrayList();
    public void add() {
        list.add("NanJing");
    }
    public int size() {
        return list.size();
    }
}
class ThreadA extends Thread {
    private MyList myList;
    public ThreadA(MyList myList) {
        this.myList = myList;
    }
    @Override
    public void run() {
        try {
            for (int i=0; i<10; i++) {
                myList.add();
                System.out.println("添加了" + (i+1) + "个元素");
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class ThreadB extends Thread {
    private MyList myList;
    public ThreadB(MyList myList) {
        this.myList = myList;
    }
    @Override
    public void run() {
        try {
            while (true) {
                int myListSize = myList.size();
                if (myListSize == 5) {
                    System.out.println("myList长度等于5了,线程需要退出了");
                    throw new InterruptedException();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行结果:

分析:

程序运行后的效果如上图所示:

虽然 ThreadA 和 ThreadB 实现了通信,但有一个弊端就是,ThreadB 需要不停地通过 while 语句轮询机制来检测某一个条件,这样会浪费CPU资源。

如果轮询的时间间隔很小,更浪费CPU资源;如果轮询的时间间隔很大,有可能会取不到想要得到的数据。所以就需要有一种机制来实现减少CPU的资源浪费,而且还可以实现在多个线程间通信,它就是 “等待/通知”(wait/notify)机制。

(2)什么是等待/通知机制

等待/通知机制在生活中比比皆是,比如在就餐时就会出现

厨师和服务员之间的交互要在 “菜品传递台”上,在这期间会有几个问题:

问题一:厨师做完一道菜的时间不确定,所以厨师将菜品放到“菜品传递台”上的时间也不确定。

问题二:服务员取到菜的时间取决于厨师,所以服务员就有“等待”(wait)的状态。

问题三:服务员如何能取到菜呢?这又得取决于厨师,厨师将菜放在“菜品传递台”上,其实就相当于一种通知(notfiy),这时服务员才可以拿到菜并交给就餐者。

问题四:在这个过程中出现了“等待/通知”机制。

(3)等待/通知机制的实现

方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait()所在的代码行处停止执行,直到接到通知或被中断为止。在调用wait()之前,线程必须获取该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。在执行wait()方法后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁。如果调用wait()时没有持有适当的锁,则抛出 IllegalMonitorStateException,它是 RuntimeException的一个子类,因此,不需要 try-catch 语句进行捕捉异常。

方法notify()也要在同步方法或同步块中调用,即在调用前,线程也必须获取该对象的对象级别锁。如果调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并是它等待获取该对象的对象锁。需要说明是:在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。当第一个获得了该对象锁的wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则几遍该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,还会继续阻塞在wait状态,直到这个对象发出一个notify 或 notifyAll。

总结一下:

wait:使线程停止运行

notify:使停止的线程继续运行

验证1:在调用wait()之前,线程必须获取该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。如果调用wait()时没有持有适当的锁,则抛出IllegalMonitorStateException。

@Test
public void test1() {
    try {
        String str = new String("");
        str.wait();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

执行结果:

验证2:调用了wait(),线程会在调用wait()所在的代码行处停止执行,直到接到通知或被中断为止。

@Test
public void test2() {
    String lockStr = new String("");
    System.out.println("sync 上面");
    try {
         synchronized (lockStr) {
             System.out.println("进入 sync");
             lockStr.wait();
             System.out.println("wait 下的代码");
         }
         System.out.println("sync 下面的代码");
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
}

执行结果:

结果分析:

线程执行了wait()方法后,程序就停止不前,不继续向下运行了。如何使呈等待wait状态的线程继续运行呢?答案就是使用notify()方法。

@Test
public void test3() {
   try {
       Object lock = new Object();
       ThreadC3A threadC3A = new ThreadC3A(lock);
       threadC3A.start();
       Thread.sleep(3000);
       ThreadC3B threadC3B = new ThreadC3B(lock);
       threadC3B.start();
   } catch (Exception e) {
       e.printStackTrace();
   }
}
class ThreadC3A extends Thread {
    private Object lock;
    public ThreadC3A(Object lock) {
        this.lock = lock;
    }
    @Override
    public void run() {
        try {
            synchronized (lock) {
                System.out.println("开始 wait,Time=[" + System.currentTimeMillis() + "]");
                lock.wait();
                System.out.println("结束 wait,Time=[" + System.currentTimeMillis() + "]");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class ThreadC3B extends Thread {
    private Object lock;
    public ThreadC3B(Object lock) {
        this.lock = lock;
    }
    @Override
    public void run() {
        synchronized (lock) {
            System.out.println("开始 notify,Time=[" + System.currentTimeMillis() + "]");
            lock.notify();
            System.out.println("结束 notify,Time=[" + System.currentTimeMillis() + "]");
        }
    }
}

执行结果:
开始 wait,Time=[1659520582642]
开始 notify,Time=[1659520585652]
结束 notify,Time=[1659520585652]
结束 wait,Time=[1659520585656]

验证3:notify方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则有线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并使它等待获取该对象的对象锁。

public class TestC5 {
    @Test
    public void test1() {
        Object obj = new Object();
        MyArrayList list = new MyArrayList();
        ThreadC5B threadC5B = new ThreadC5B(obj, list);
        threadC5B.start();
        ThreadC5C threadC5C = new ThreadC5C(obj, list);
        threadC5C.start();
        ThreadC5A threadC5A = new ThreadC5A(obj, list);
        threadC5A.start();
        while (Thread.activeCount() > 1) {
        }
    }
}
class ThreadC5A extends Thread {
    private Object lock;
    private MyArrayList list;
    public ThreadC5A(Object lock, MyArrayList list) {
        this.lock = lock;
        this.list = list;
    }
    @Override
    public void run() {
        try {
            synchronized (lock) {
                for (int i=0; i<10; i++) {
                    list.add();
                    System.out.println("ThreadC5A 新增第[" + (i+1) + "]个元素");
                    if (list.size() == 5) {
                        lock.notify();
                        System.out.println("ThreadC5A 发出通知,通知等待的线程 ThreadC5B 或 ThreadC5C");
                    }
                    Thread.sleep(1000);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class ThreadC5B extends Thread {
    private Object lock;
    private MyArrayList list;
    public ThreadC5B(Object lock, MyArrayList list) {
        this.lock = lock;
        this.list = list;
    }
    @Override
    public void run() {
        try {
            while (true) {
                synchronized (lock) {
                    System.out.println("ThreadC5B 等待被通知");
                    lock.wait();
                    System.out.println("ThreadC5B 收到通知,退出");
                    return;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class ThreadC5C extends Thread {
    private Object lock;
    private MyArrayList list;
    public ThreadC5C(Object lock, MyArrayList list) {
        this.lock = lock;
        this.list = list;
    }
    @Override
    public void run() {
        try {
            while (true) {
                synchronized (lock) {
                    System.out.println("ThreadC5C 等待被通知");
                    lock.wait();
                    System.out.println("ThreadC5C 收到通知,退出");
                    return;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行结果:

结果分析:

可以看到处于wait状态的ThreadC5B线程被通知到继续执行,而ThreadC5C线程则一直将处于wait状态,无法继续执行。倘若我们将ThreadC5A线程中的 lock.notify() 改写为 lock.notifyAll(),则结果就不一样,如图所示:

发现ThreadC5B 和 ThreadC5C线程均获取到了对象锁,完成了wait()后面代码的执行。

验证4:上图的结果还可以验证在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。

结果分析:

可以看出 ThreadC5A 线程在list 长度为5的时候,则通知了 ThreadC5B 和 ThreadC5C 两个处于wait状态的线程,但是 ThreadC5B 和 ThreadC5C 线程并没有立马继续执行,因为此时 ThreadC5A 并没有释放对象锁,而是继续执行 synchronized的代码块,直到退出synchronized代码块后,ThreadC5A 才释放了对象锁,ThreadC5B 和 ThreadC5C 获得对象锁,才得以继续执行后续代码。

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们