关闭

等待/通知机制:

标签: 线程
1039人阅读 评论(1) 收藏 举报
分类:

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

通过sleep()结合while(true)死循环法来实现多个线程 间通信,创建如下代码:

public class MyList {
    private List list = new ArrayList();

    public void add(){
        list.add("abc");
    }

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

public class ThreadA extends Thread{
    private MyList list;

    public ThreadA(MyList list) {
        this.list = list;
    }

    @Override
    public void run() {
        for(int i=0;i<10;i++){
            list.add();
            System.out.println("添加了"+(i+1)+"个元素");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class ThreadB extends Thread {
    private MyList list;

    public ThreadB(MyList list) {
        this.list = list;
    }

    @Override
    public void run() {
        try {
            while (true) {
                if (list.size() >= 5) {
                    System.out.println("==5了,B线程要退出");
                    throw new InterruptedException();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public class Test{
    public static void main(String[] args){
        MyList service = new MyList();
        ThreadA a = new ThreadA();
        a.setName("A");
        a.start();
        ThreadB b = new ThreadB();
        b.setName("B");
        b.start();
    }
}

执行结果如下:
这里写图片描述

虽然两个线程实现了通信,但是有一个弊端就是,线程B不停的通过while(true)语句进行轮询机制来监测某一个条件。这样会浪费CPU的资源。
如果轮询的间隔更短,更浪费CPU的资源。如果间隔很大则有可能取不到想要的资源。所有需要有一种机制来实现减少CPU的资源浪费,而且还可以实现线程间通信,它就是“wait/notify”机制。

什么是等待/通知机制:

等待/通知机制在生活中比比皆是,比如就餐时就会出现,流程如下图:
厨师和服务员之间的交互要在“菜品传递台”上,在这期间会有如下几个问题:

  1. 厨师师做完一道菜的时间不确定,所以厨师将菜品放到“菜品传递台”上的时间也不确定。
  2. 服务员取到菜的时间取决于厨师,所以服务员就有“等待”的状态。
  3. 服务员如何能取到菜呢?这又得取决于厨师,厨师将菜放在“菜品传递台”上,其实就相当于一种通知,这时候服务员才可以拿到菜并交给就餐者。
  4. 这个过程中出现了“等待/通知”机制。
    这里写图片描述
    其实在前面的示例中多线程之间也可以实现通信,原因就是多个线程共同访问同一个变量,但是那种同步机制不是“等待/通知”,两个线程完全是主动的读取一个共享变量,在花费读取时间的基础上,读到的值是不是想要的,并不能完全确定。所以需要一种“等待/通知”的机制来满足上面的要求。

    等待/通知机制的实现:

    方法wait()的作用是使当前执行代码的线程等待,wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait()所在的代码行处停止执行,直到接到通知或被终止为止。在调用wait()之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步代码块中调用wait()方法。在执行wait()方法后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁。如果调用wait()时没有持有适当的锁,则抛出IllegaMonitorStateException,它是一个RuntimeException的一个子类,因此,不需要try-catch语句进行捕捉异常。
    方法notify()也要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁。如果调用notify()时没有持有适当的锁,也会抛出IllegaMonitorStateException。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并使它等待获取该对象的对象锁。需要说明的是,在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象的锁。当第一个获得了该对象锁的wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,还会继续阻塞wait状态,直到这个对象发出一个notify或notifyAll。
    用一句话来总结一下wait和notify:wait使线程停止运行,而notify使停止的线程继续运行。

public class Test1 {
    public static void main(String[] args) {
        String newString = new String("");
        try {
            newString.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行结果如下:
这里写图片描述

出现异常的原因是没有“对象监视器”,也就是没有同步加锁。
继续创建如下代码:

public class Test2 {
    public static void main(String[] args) {
        String lock = new String();
        System.out.println("syn上面代码");
        synchronized(lock){
            System.out.println("syn第一行");
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("wait下面的代码");
        }
        System.out.println("syn下面代码");
    }
}

执行结果如下:
enter description here
但线程永远不能等待下去,那样程序就停不下来,不能继续向下运行了。这里需要使用notify()方法。创建如下代码:

public class Thread1 extends Thread{
    private Object lock;

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

    @Override
    public void run() {
        synchronized(lock){
            System.out.println("开始   wait time="+System.currentTimeMillis());
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("结束   wait time="+System.currentTimeMillis());
        }
    }
}
public class Thread2 extends Thread{
    private Object lock;

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

    @Override
    public void run() {
        synchronized(lock){
            System.out.println("开始   notify time="+System.currentTimeMillis());
            try {
                lock.notify();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("结束   notify time="+System.currentTimeMillis());
        }
    }
}
public class Test {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        Thread1 t1 = new Thread1(lock);
        t1.start();
        Thread.sleep(3000);
        Thread2 t2 = new Thread2(lock);
        t2.start();
    }
}

执行结果如下:
enter description here
从结果可以看出,3秒后线程被notif通知唤醒。再创建如下代码:

public class MyList {
    private static List list = new ArrayList();

    public static void add(){
        list.add("string");
    }
    public static int size(){
        return list.size();
    }
}
public class ThreadA extends Thread{
    private Object lock;
    public ThreadA(Object lock){
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized(lock){
            if(MyList.size()!=5){
                System.out.println("wait begin "+System.currentTimeMillis());
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("wait end "+System.currentTimeMillis());
            }
        }
    }
}
public class ThreadB extends Thread{
    private Object lock;
    public ThreadB(Object lock){
        this.lock = lock;
    }

    public void run() {
        synchronized(lock){
            for(int i=0;i<10;i++){
                MyList.add();
                if(MyList.size()==5){
                    lock.notify();
                    System.out.println("已发出通知!");
                }
                System.out.println("添加了"+(i+1)+"个元素");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
public class Run {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        ThreadA threadA = new ThreadA(lock);
        threadA.start();
        Thread.sleep(50);
        ThreadB threadB = new ThreadB(lock);
        threadB.start();
    }
}

执行结果如下:
enter description here
通过输出日志可以看出wait end是最后输出的,这也说明notify()方法执行后并不立即释放锁。
关键字synchronized可以将任何一个Object对象作为同步对象来看待,而,而Java为每个Object对象都实现了wait()和notify()方法,它们必须用在被synchronized同步的Object的临界区内。通过调用wait()方法可以使处于临界区内的线程进入等待状态,同时释放被同步对象的锁。而notify操作可以唤醒一个因调用了wait操作而处于阻塞状态中的线程,使其进入就绪状态。被重新唤醒的线程会试图重新获得临界区的控制权,也就是锁,并继续执行临界区内的wait后的代码。如果发出notify操作时没有处于阻塞的线程,那么该命令会忽视。
wait方法可以使调用该方法的线程释放共享资源的锁,然后从运行状态退出,进入等待队列,直到被再次唤醒。
notify方法可以随机唤醒等待队列中等待同一共享资源的“一个”线程,并使该线程退出等待队列,进入可运行队列,也就是notify方法仅通知一个线程。
notifyAll方法可以使所有正在等待的队列中等待同一共享资源的“全部”线程从等待状态,进入可运行状态。此时,优先级最高的那个线程最先执行,但也有可能随机执行,因为这取决于JVM虚拟机的实现。
通过线程的方法改变线程的状态具体如下:
enter description here

  • 新建一个新的线程对象后,再调用它的start()方法,系统会为此线程分配CPU资源,使其处于Runnable(可运行)状态,这是一个准备运行阶段。如果线程抢占到CPU资源,此线程就处于Running(运行)状态。
  • Runnable和Running状态可相互切换,因为有可能线程运行一段时间后,有其他高优先级的线程抢占了CPU资源,这个时候线程就从Running状态变为Runnable状态。
    线程进入Runnable状态大体分为如下5种情况:

  • 调用sleep()方法后经过的时间超过了指定的休眠时间。

  • 线程调用的阻塞IO已经返回,阻塞方法执行完毕。
    • 线程成功获得了试图同步的监视器。
    • 线程正在等待某个通知,其他线程发出了通知。
    • 处于挂起状态的线程调用了resume恢复方法。
      3.Blocked是阻塞的意思,例如遇到一个IO操作,此时CPU处于空闲状态,可能会转而把CPU时间片分配给其他线程,这时也可以称为“暂停”状态。Blocked状态结束后进入Runnable状态,等待系统重新分配资源。
      出现阻塞的情况大体分为如下5中:
      1、 线程调用sleep方法,主动放弃占用的处理器资源。
      2、 线程调用了阻塞式IO方法,在该方法返回前,该线程被阻塞。
      3、 线程试图获得一个同步监视器,但该监视器正在被其他线程所持有。
      4、 线程等待某个通知。
      5、 线程调用了suspend方法将该线程挂起。此方法容易导致死锁,尽量避免使用该方法。
      4.run()方法运行结束后进入销毁阶段,整个线程执行完毕。
      每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列中存储了将要获得锁的线程。阻塞对象中存储了被阻塞的线程。一个线程唤醒后才会进入就绪队列,等待CPU的执行。反之,一个线程调用wait()方法后,就会进入阻塞队列,等待下一次被唤醒。

方法wait()锁释放与notify()锁不释放:

当方法wait()被执行后,锁被自动释放,但执行完notify()方法,锁却不自动释放。创建如下代码:

public class Service {
    public void testMethod(Object lock){
        synchronized(lock){
            System.out.println("begin wait()");
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end wait()");
        }
    }
}
public class ThreadA extends Thread{
    private Object lock;
    public ThreadA(Object lock){
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}
public class ThreadB extends Thread{
    private Object lock;

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

    @Override
    public void run() {
        Service service  = new Service();
        service.testMethod(lock);
    }
}
public class Test {
    public static void main(String[] args) {
        Object lock = new Object();
        ThreadA threadA = new ThreadA(lock);
        threadA.start();
        ThreadB threadB = new ThreadB(lock);
        threadB.start();
    }
}

执行结果如下:
enter description here
如果将wait()方法改为sleep()方法,就成了同步的效果。如下图:
enter description here
还需要验证方法notify()执行后不释放锁。创建如下代码:

public class Service {
    public void testMethod(Object lock){
        synchronized(lock){
            System.out.println("begin wait() ThreadName="+Thread.currentThread().getName());
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end wait()ThreadName="+Thread.currentThread().getName());
        }
    }

    public void synNotifyMethod(Object lock){
        synchronized(lock){
            System.out.println("begin notify()"+Thread.currentThread().getName()+" time"+System.currentTimeMillis());
            lock.notify();
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end notify()"+Thread.currentThread().getName()+" time"+System.currentTimeMillis());
        }
    }
}
public class ThreadA extends Thread{
    private Object lock;
    public ThreadA(Object lock){
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}

public class NotifyThread extends Thread{
    private Object lock;
    public NotifyThread(Object lock){
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.synNotifyMethod(lock);
    }
}
public class SynNotifyMethodThread extends Thread{
    private Object lock;
    public SynNotifyMethodThread(Object lock){
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.synNotifyMethod(lock);
    }
}
public class Test {
    public static void main(String[] args) {
        Object lock = new Object();
        ThreadA threadA = new ThreadA(lock);
        threadA.start();
        NotifyThread notifyThread = new NotifyThread(lock);
        notifyThread.start();
        SynNotifyMethodThread methodThread = new SynNotifyMethodThread(lock);
        methodThread.start();
    }
}

执行结果如下:

enter description here
说明必须执行完notify()方法所在的同步synchronized代码块后才释放锁。

当interrupt方法遇到wait方法:

当线程呈wait()状态时,调用对象的interrupt()方法会出现InterruptedException异常。创建如下代码:

public class Service {

    public void testMethod(Object lock){
        synchronized(lock){
            System.out.println("begin wait");
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("出现异常了,因为呈wait状态的线程被interrupt");
            }
            System.out.println("end wait");
        }
    }
}
public class ThreadA extends Thread{
    private Object lock;
    public ThreadA(Object lock){
        this.lock = lock;
    }
    @Override
    public void run() {
        Service service  = new Service();
        service.testMethod(lock);
    }
}
public class Test {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        ThreadA threadA =  new ThreadA(lock);
        threadA.start();
        Thread.sleep(5000);
        threadA.interrupt();
    }
}

执行结果如下:
enter description here
通过上面的代码可以总结如下结论:

  1. 执行完同步代码块就会释放对象的锁。
  2. 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放。
  3. 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放对象锁,而此对象会进入线程等待池中等待被唤醒。
    ### 只通知一个线程:
    调用notify()一次只随机通知一个线程进行唤醒。创建如下代码:
public class Service {
    public void testMethod(Object lock) {
        synchronized (lock) {
            System.out.println("begin wait() ThreadName=" + Thread.currentThread().getName());
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end wait() ThreadName=" + Thread.currentThread().getName());
        }
    }
}
public class ThreadA extends Thread{
    private Object lock;
    public ThreadA(Object lock){
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}
public class ThreadB extends Thread{
    private Object lock;
    public ThreadB(Object lock){
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}
public class ThreadC extends Thread{
    private Object lock;
    public ThreadC(Object lock){
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}
public class NotifyThread extends Thread{
    private Object lock;
    public NotifyThread(Object lock){
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized(lock){
            lock.notify();
        }
    }
}
public class Test {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();

        ThreadA threadA = new ThreadA(lock);
        threadA.start();
        ThreadB threadB = new ThreadB(lock);
        threadB.start();
        ThreadC threadC = new ThreadC(lock);
        threadC.start();

        Thread.sleep(1000);

        NotifyThread notifyThread = new NotifyThread(lock);
        notifyThread.start();
    }
}

执行结果如下:
enter description here
方法notify()方法只能唤醒一个线程。如果多次调用notify()方法,会随机将等待wait状态的线程进行唤醒。更改NotifyThread类的代码如下:

public class NotifyThread extends Thread{
    private Object lock;
    public NotifyThread(Object lock){
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized(lock){
            lock.notify();
            lock.notify();
            lock.notify();
            lock.notify();
        }
    }
}

执行结果如下:
enter description here
多次调用notify()方法唤醒全部waiting中的线程。

唤醒所有线程:

前面示例中通过多次调用notify()方法来实现唤醒3个线程,但并不能保证系统中仅有3个线程,也就是若notify()方法的调用次数小于线程对象的数量,会出现有部分线程对象无法唤醒的情况。为了唤醒全部线程,可以使用notifyAll()方法。
创建如下代码,将上面项目中NotifyThread.java类使用的方法改为notifyAll()即可。运行结果如下:
enter description here

方法wait(long)的使用:

带一个参数的wait(long)方法的功能是等待某一时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒。创建如下代码:

public class MyRunnable {
    static private Object lock = new Object();
    static private Runnable runnable = new Runnable(){
        public void run() {
            synchronized(lock){
                System.out.println("wait begin time="+System.currentTimeMillis());
                try {
                    lock.wait(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("wait  end  time="+System.currentTimeMillis());
            }
        }
    };
    public static void main(String[] args) {
        Thread t1 = new Thread(runnable);
        t1.start();
    }
}

执行结果如下:
这里写图片描述
当然也可以在5秒内有其他线程唤醒,修改代码如下:

public class MyRunnable {
    static private Object lock = new Object();
    static private Runnable runnable = new Runnable(){
        public void run() {
            synchronized(lock){
                System.out.println("wait begin time="+System.currentTimeMillis());
                try {
                    lock.wait(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("wait  end  time="+System.currentTimeMillis());
            }
        }
    };

    static private Runnable runnable1 = new Runnable(){
        public void run() {
            synchronized(lock){
                System.out.println("notify start="+System.currentTimeMillis());
                lock.notify();
                System.out.println("notify end="+System.currentTimeMillis());
            }
        }
    };
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(runnable);
        t1.start();
        Thread.sleep(3000);
        Thread t2 = new Thread(runnable1);
        t2.start();
    }
}

执行结果如下:
enter description here
通过打印结果可以看出是线程t1被提前唤醒。

通知过早:

如果通知过早,则会打乱程序的正常运行逻辑.创建如下代码:

public class MyRun {

    private String lock = new String("");

    private Runnable runnableA = new Runnable(){
        @Override
        public void run() {
            synchronized(lock){
                System.out.println("begin wait");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("end wait");
            }
        }
    };

    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) {
        MyRun myRun = new MyRun();
        Thread t1 = new Thread(myRun.runnableA);
        t1.start();
        Thread t2 = new Thread(myRun.runnableB);
        t2.start();
    }
}

执行结果如下:
enter description here
修改main的代码如下:

public static void main(String[] args) throws InterruptedException {
        MyRun myRun = new MyRun();
        Thread t2 = new Thread(myRun.runnableB);
        t2.start();
        Thread.sleep(100);
        Thread t1 = new Thread(myRun.runnableA);
        t1.start();
    }

执行结果如下:
这里写图片描述
如果先通知了,则wait方法也必要执行了。在修改MyRun代码如下:

public class MyRun {

    private String lock = new String("");
    private boolean isFitstRunB = false;
    private Runnable runnableA = new Runnable() {
        @Override
        public void run() {
            synchronized (lock) {
                while (isFitstRunB == false) {
                    System.out.println("begin wait");
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("end wait");
                }
            }
        }
    };

    private Runnable runnableB = new Runnable() {
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println("begin notify");
                lock.notify();
                System.out.println("end notify");
                isFitstRunB = true;
            }
        }
    };

    public static void main(String[] args) throws InterruptedException {
        MyRun myRun = new MyRun();
        Thread t2 = new Thread(myRun.runnableB);
        t2.start();
        Thread.sleep(100);
        Thread t1 = new Thread(myRun.runnableA);
        t1.start();
    }
}

执行结果如下:
enter description here

继续修改main方法代码,如下:

public static void main(String[] args) throws InterruptedException {
        MyRun myRun = new MyRun();
        Thread t1 = new Thread(myRun.runnableA);
        t1.start();
        Thread.sleep(100);
        Thread t2 = new Thread(myRun.runnableB);
        t2.start();
    }

执行结果如下:
enter description here

等待wait的条件发生变化:

在使用wait/notify模式时,还需要注意另外一种情况,也就是wait等待的条件发生了变化,也容易造成程序逻辑的混乱个。创建如下代码:

public class ValueObject {
    public static List<String> list = new ArrayList<String>();
}
public class Add {
    private String lock;
    public Add(String lock){
        this.lock = lock;
    }

    public void add(){
        synchronized(lock){
            ValueObject.list.add("anyString");
            lock.notifyAll();
        }
    }
}
public class Subtract {
    private String lock;
    public Subtract(String lock){
        this.lock = lock;
    }

    public void subtract(){
        synchronized(lock){
            if(ValueObject.list.size()==0){
                System.out.println("wait begin ThreadName="+Thread.currentThread().getName());
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("wait end ThreadName="+Thread.currentThread().getName());
            }
            ValueObject.list.remove(0);
            System.out.println("list size=" + ValueObject.list.size());
        }
    }
}
public class ThreadAdd extends Thread{
    private Add add;

    public ThreadAdd(Add add){
        this.add = add;
    }

    @Override
    public void run() {
        add.add();
    }
}
public class ThreadSubtract extends Thread{
    private Subtract subtract;

    public ThreadSubtract(Subtract subtract){
        this.subtract = subtract;

    }

    @Override
    public void run() {
        subtract.subtract();
    }
}
public class Run {
    public static void main(String[] args) throws InterruptedException {
        String lock = new String("");
        Subtract s = new Subtract(lock);
        ThreadSubtract threadSubtract1 = new ThreadSubtract(s);
        threadSubtract1.setName("threadSubtract1");
        threadSubtract1.start();

        ThreadSubtract threadSubtract2 = new ThreadSubtract(s);
        threadSubtract2.setName("threadSubtract2");
        threadSubtract2.start();

        Thread.sleep(1000);

        Add add = new Add(lock);

        ThreadAdd threadAdd  = new ThreadAdd(add);
        threadAdd.setName("addThread");
        threadAdd.start();
    }
}

出现异常的原因是因为有两个实现remove()操作的线程,他们在Thread.sleep(1000);之前都执行了wait()方法,呈等待状态,当操作的线程在1秒后被运行时,通知了所有呈wait等待状态的减操作的线程,那么第一个实现减操作的线程能正确删除list中索引为0的数据。但第二个线程实现减操作的线程会出现索引溢出的异常,因为list中仅仅添加了一个数据,也只能删除一个数据。解决这个问题,修改代码如下:

public class Subtract {
    private String lock;
    public Subtract(String lock){
        this.lock = lock;
    }

    public void subtract(){
        synchronized(lock){
            while(ValueObject.list.size()==0){
                System.out.println("wait begin ThreadName="+Thread.currentThread().getName());
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("wait end ThreadName="+Thread.currentThread().getName());
            }
            ValueObject.list.remove(0);
            System.out.println("list size=" + ValueObject.list.size());
        }
    }
}

执行结果如下:
enter description here

生产者/消费者模式实现:

等待/通知模式最经典的案例就是”生产/消费者”模式。但此模式在使用上有几种“变形”,但是原理都是基于wait/notify的。

  1. 一生产与一消费,创建如下代码:
public class ValueObject {
    public static String value="";
}
/**
 * 生产者
 */
public class P {
    private String lock;
    public P(String lock){
        this.lock = lock;
    }

    public void setValue(){
        synchronized(lock){
            if(!ValueObject.value.equals("")){
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            String value = System.currentTimeMillis()+"_"+System.nanoTime();
            System.out.println("set的值是 "+value);
            ValueObject.value = value;
            lock.notify();
        }
    }
}
/**
 *消费者
 */
public class C {
    private String lock;
    public C(String lock){
        this.lock = lock;
    }

    public void getValue(){
        synchronized(lock){
            if(ValueObject.value.equals("")){
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("get的值是"+ValueObject.value);
            ValueObject.value = "";
            lock.notify();
        }
    }
}
public class ThreadP extends Thread{
    private P p;
    public ThreadP(P p){
        this.p = p;
    }
    @Override
    public void run() {
        while(true){
            p.setValue();
        }
    }
}
public class ThreadC extends Thread {
    private C c;

    public ThreadC(C c) {
        this.c = c;
    }

    @Override
    public void run() {
        while (true) {
            c.getValue();
        }
    }
}

public class Run {
    public static void main(String[] args) {
        String lock = new String("");
        P p = new P(lock);
        C c = new C(lock);
        ThreadP threadP = new ThreadP(p);
        ThreadC threadC = new ThreadC(c);
        threadP.start();
        threadC.start();
    }
}

执行结果如下:
这里写图片描述
2.多生产与多消费:操作值-假死:
“假死”的现象其实就是线程进入WAITING等待状态。如果全部线程进入WAITING状态,则程序就不再执行任何业务功能了,整个项目呈停止状态。这在生产者与消费者模式中经常遇到。创建如下代码:

public class C {
    private String lock;

    public C(String lock){
        this.lock = lock;
    }

    public void getValue(){
        synchronized(lock){
            while(ValueObject.value.equals("")){
                System.out.println("消费者 "+Thread.currentThread().getName()+" WAITING");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("消费者 "+Thread.currentThread().getName()+" RUNNABLE");
            ValueObject.value = "";
            lock.notify();
        }
    }
}
public class P {
    private String lock;
    public P(String lock){
        this.lock = lock;
    }
    public void setValue(){
        synchronized(lock){
            while(!ValueObject.value.equals("")){
                try {
                    System.out.println("生产者"+Thread.currentThread().getName()+"WAITING");
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("生产者"+Thread.currentThread().getName()+"RUNNABLE");
            ValueObject.value = System.currentTimeMillis()+" - "+System.nanoTime();
            lock.notify();
        }
    }
}public class ThreadC extends Thread{
    private C c;

    public ThreadC(C c){
        this.c = c;
    }

    @Override
    public void run() {
        while(true){
            c.getValue();
        }
    }
}
public class ThreadP extends Thread{
    private P p;
    public ThreadP(P p){
        this.p = p;
    }

    @Override
    public void run() {
        while(true){
            p.setValue();
        }
    }
}
public class ValueObject {
    public static String value = "";
}
public class Run {
    public static void main(String[] args) throws InterruptedException {
        String lock = new String("");
        P p = new P(lock);
        C c = new C(lock);
        ThreadP[] threadPs = new ThreadP[2];
        ThreadC[] threadCs = new ThreadC[2];
        for(int i=0;i<2;i++){
            threadPs[i] = new ThreadP(p);
            threadPs[i].setName("生产者"+i);
            threadCs[i] = new ThreadC(c);
            threadCs[i].setName("消费者"+i);
            threadPs[i].start();
            threadCs[i].start();
        }

        Thread.sleep(10000);

        Thread[] threadArray = new Thread[Thread.currentThread().getThreadGroup().activeCount()];
        Thread.currentThread().getThreadGroup().enumerate(threadArray);
        for(int i=0;i<threadArray.length;i++){
            System.out.println(threadArray[i].getName()+" "+threadArray[i].getState());
        }
    }

执行结果如下:
这里写图片描述
程序运行后很有可能出现假死状态.
从打印信息来看,呈假死状态的进程中所有线程都呈WAITING状态。为什么会出现这种情况,代码中已经使用了notify/wait了?
在代码中确实已经使用了notify/wait进行通信了,但不保证唤醒的就是异类,也许是同类。例如“生产者”唤醒“生产者”,或“消费者”唤醒“消费者”这样的情况。如果经常这样,就会导致线程不能继续执行下去,所有线程都成WAITING状态,程序最后也呈“假死”状态,不能继续运行下去。
通过打印结果分析线程怎么进入“假死”状态的,这是将打印结果放到了
enter description here
该程序导致假死的原因就如下图:
enter description here
3.多生产与多消费:操作值:
解决“假死”的情况很简单,只需将notify()方法改为notifyAll()方法就行,因为notifyAll()不止通知一个线程。
4.一生产与一消费:操作栈:
通过代码实现生产者向堆栈List对象放入数据,使消费者从List堆栈中取出数据。List最大容量是1,实验只有一个生产者与一个消费者。创建如下代码:

public class MyStack {
    private List list = new ArrayList();

    synchronized public void push(){
        if(list.size()==1){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        list.add("anyString= "+Math.random());
        this.notify();
        System.out.println("push="+list.size());
    }

    synchronized public String pop(){
        String returnValue = "";
        if(list.size()==0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        returnValue = ""+list.get(0);
        list.remove(0);
        this.notify();
        System.out.println("pop="+list.size());
        return returnValue;
    }
}
public class P {
    private MyStack myStack;

    public P(MyStack myStack){
        this.myStack = myStack;
    }

    public void pushService(){
        myStack.push();
    }
}

public class C {
    private MyStack myStack;

    public C(MyStack myStack){
        this.myStack = myStack;
    }

    public void popService(){
        System.out.println("pop= "+myStack.pop());
    }
}
public class PThread extends Thread{
    private P p;
    public PThread(P p){
        this.p = p;
    }

    @Override
    public void run() {
        while(true){
            p.pushService();
        }
    }
}
public class CThread extends Thread{
    private C c;
    public CThread(C c){
        this.c = c;
    }
    @Override
    public void run() {
        while(true){
            c.popService();
        }
    }
}
public class Run {
    public static void main(String[] args) {
        MyStack myStack = new MyStack();
        P p = new P(myStack);
        C c = new C(myStack);
        PThread pThread = new PThread(p);
        CThread cThread = new CThread(c);
        pThread.start();
        cThread.start();
    }
}

执行结果如下:
enter description here
程序运行结果size()不会大于1.
通过使用生产者/消费者模式,容器的size()的值不会大于1,这也是本例想要实现的效果,值在0和1之间切换,也就是生成和消费这两个过程在交替执行。
5.一生成与多消费—操作栈:解决wait条件改变和假死
使用一个生产者向堆栈list对象中放入数据,而多个消费者从list堆栈中取出数据,list的最大容量还是1。创建如下代码:
修改上面代码的Run类代码,其他不变:

public class Run {
    public static void main(String[] args) {
        MyStack myStack = new MyStack();
        P p = new P(myStack);
        C c1 = new C(myStack);
        C c2 = new C(myStack);
        C c3 = new C(myStack);
        C c4 = new C(myStack);
        C c5 = new C(myStack);
        PThread pThread = new PThread(p);
        CThread cThread1 = new CThread(c1);
        CThread cThread2 = new CThread(c2);
        CThread cThread3 = new CThread(c3);
        CThread cThread4 = new CThread(c4);
        CThread cThread5 = new CThread(c5);
        pThread.start();
        cThread1.start();
        cThread2.start();
        cThread3.start();
        cThread4.start();
        cThread5.start();
    }
}

执行结果如下:
enter description here
出现异常,出现这个问题的原因是:
在MyStack.java类中使用了if作为条件判断,代码如下:

synchronized public String pop() {
        String returnValue = "";
        if (list.size() == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        returnValue = "" + list.get(0);
        list.remove(0);
        this.notify();
        System.out.println("pop=" + list.size());
        return returnValue;
    }

因为条件改变时并没有得到及时的响应,所以多个呈wait状态的线程被唤醒,继而执行list.remove(0)代码而出现异常。解决这个问题的方法是,将if改为while语句即可。修改代码如下:

synchronized public void push(){
        while(list.size()==1){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        list.add("anyString= "+Math.random());
        this.notifyAll();
        System.out.println("push="+list.size());
    }
    synchronized public String pop() {
        String returnValue = "";
        while (list.size() == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        returnValue = "" + list.get(0);
        list.remove(0);
        this.notify();
        System.out.println("pop=" + list.size());
        return returnValue;
    }

执行结果如下:
这里写图片描述
执行结果没有异常,但是却出现了死锁。解决办法还是使用notifyAll()方法。代码两处使用了notify都改为notifyAll即可。
6.多生产与一消费
修改Run类的代码如下:

public class Run {
    public static void main(String[] args) {
        MyStack myStack = new MyStack();
        P p1 = new P(myStack);
        P p2 = new P(myStack);
        P p3 = new P(myStack);
        P p4 = new P(myStack);
        P p5 = new P(myStack);

        C c = new C(myStack);

        PThread pThread1 = new PThread(p1);
        PThread pThread2 = new PThread(p2);
        PThread pThread3 = new PThread(p3);
        PThread pThread4 = new PThread(p4);
        PThread pThread5 = new PThread(p5);
        pThread1.start();
        pThread2.start();
        pThread3.start();
        pThread4.start();
        pThread5.start();

        CThread cThread = new CThread(c);
        cThread.start();
    }
}

7.多生产与多消费:操作栈
示例还是基于上面的代码,修改Run的代码如下:

public class Run {
    public static void main(String[] args) {
        MyStack myStack = new MyStack();
        P p1 = new P(myStack);
        P p2 = new P(myStack);
        P p3 = new P(myStack);
        P p4 = new P(myStack);
        P p5 = new P(myStack);

        C c1 = new C(myStack);
        C c2 = new C(myStack);
        C c3 = new C(myStack);
        C c4 = new C(myStack);
        C c5 = new C(myStack);

        PThread pThread1 = new PThread(p1);
        PThread pThread2 = new PThread(p2);
        PThread pThread3 = new PThread(p3);
        PThread pThread4 = new PThread(p4);
        PThread pThread5 = new PThread(p5);
        pThread1.start();
        pThread2.start();
        pThread3.start();
        pThread4.start();
        pThread5.start();

        CThread cThread1 = new CThread(c1);
        CThread cThread2 = new CThread(c2);
        CThread cThread3 = new CThread(c3);
        CThread cThread4 = new CThread(c4);
        CThread cThread5 = new CThread(c5);
        cThread1.start();
        cThread2.start();
        cThread3.start();
        cThread4.start();
        cThread5.start();
    }
}

执行结果如下:
enter description here
从程序运行结果看,list对象的size()并没有超过1.

通过管道进行线程间通信:字节流

在Java语言中提供了各种各样的输入/输出流Stream,使我们能够很方便地对数据进行操作,其中管道流(pipeStream)是一种特殊的流,用于不同线程间直接传送数据。一个线程发送数据到输出管道,另一个线程从输入管道中读数据。通过使用管道,实现不同线程间的通信,而无须借助类似临时文件之类的东西。
在JDK中提供了4个类来使线程间可以进行通信:
1)、PipedInputStream和PipedOutputStream。
2)、PipedReader和PipedWriter。

创建如下代码:

public class ReadData {
    public void readMethod(PipedInputStream inputStream){
        try{
            System.out.println("read:");
            byte[] byteArray = new byte[20];
            int readLength = inputStream.read(byteArray);
            while(readLength!=-1){
                String newData = new String(byteArray,0,readLength);
                System.out.println("read:"+newData);
                readLength = inputStream.read(byteArray);
            }
            System.out.println();
            inputStream.close();
        }catch(Exception e){

        }
    }
}
public class WriteData {
    public void writeMethod(PipedOutputStream outputStream){
        try{
            System.out.println("write:");
            for(int i=0;i<300;i++){
                String outData = ""+(i+1);
                outputStream.write(outData.getBytes());
                System.out.println("write:"+outData);
            }
            outputStream.close();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}
public class ThreadRead extends Thread{
    private ReadData readData;
    private PipedInputStream inputStream;

    public ThreadRead(ReadData readData,PipedInputStream inputStream){
        this.readData = readData;
        this.inputStream = inputStream;
    }

    @Override
    public void run() {
        readData.readMethod(inputStream);
    }
}
public class ThreadWrite extends Thread{
    private WriteData writeData;
    private PipedOutputStream outputStream;

    public ThreadWrite(WriteData writeData,PipedOutputStream outputStream){
        this.writeData = writeData;
        this.outputStream = outputStream;
    }

    @Override
    public void run() {
        writeData.writeMethod(outputStream);
    }
}
public class Run {
    public static void main(String[] args) throws IOException, InterruptedException {
        PipedInputStream inputStream = new PipedInputStream();
        PipedOutputStream outputStream = new PipedOutputStream();

        WriteData writeData = new WriteData();
        ReadData readData = new ReadData();

        ThreadWrite threadWrite = new ThreadWrite(writeData, outputStream);
        ThreadRead threadRead = new ThreadRead(readData, inputStream);
        //使两个线程之间通信
        outputStream.connect(inputStream);

        threadRead.start();

        Thread.sleep(2000);

        threadWrite.start();
    }
}

执行结果如下:
这里写图片描述
enter description here
使用代码outputStream.connect(inputStream)或inputStream.connect(outputStream)使两个线程产生通信链接。这样可以将数据进行输出输入。
从输出结果来看,两个线程通过管道流成功进行数据的传输。

通过管道进行线程间通信:字符流

在管道中还可以传递字符流。创建如下代码:

public class ReadData {
    public void readMethod(PipedReader reader){
        try{
            System.out.println("read:");
            char[] charArray = new char[20];
            int readLength = reader.read(charArray);
            while(readLength!=-1){
                String newData = new String(charArray,0,readLength);
                System.out.println("read:"+newData);
                readLength = reader.read(charArray);
            }
            System.out.println();
            reader.close();
        }catch(Exception e){

        }
    }
}
public class WriteData {
    public void writeMethod(PipedWriter writer){
        try{
            System.out.println("write:");
            for(int i=0;i<300;i++){
                String outData = ""+(i+1);
                writer.write(outData);
                System.out.println("write:"+outData);
            }
            writer.close();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}
public class ThreadRead extends Thread{
    private ReadData readData;
    private PipedReader reader;

    public ThreadRead(ReadData readData,PipedReader reader){
        this.readData = readData;
        this.reader = reader;
    }

    @Override
    public void run() {
        readData.readMethod(reader);
    }
}
public class ThreadWrite extends Thread{
    private WriteData writeData;
    private PipedWriter writer;

    public ThreadWrite(WriteData writeData,PipedWriter writer){
        this.writeData = writeData;
        this.writer = writer;
    }

    @Override
    public void run() {
        writeData.writeMethod(writer);
    }
}
public class Run {
    public static void main(String[] args) throws IOException, InterruptedException {
        PipedReader reader = new PipedReader();
        PipedWriter writer = new PipedWriter();

        WriteData writeData = new WriteData();
        ReadData readData = new ReadData();

        ThreadWrite threadWrite = new ThreadWrite(writeData, writer);
        ThreadRead threadRead = new ThreadRead(readData, reader);
        //使两个线程之间通信
        writer.connect(reader);

        threadRead.start();

        Thread.sleep(2000);

        threadWrite.start();
    }
}

执行结果如下:
enter description here
enter description here
和上面的执行结果差别不大。

实战:等待/通知之交换备份

本例创建20个线程,其中10个线程是将数据备份到A数据库中,另外10个线程将数据备份到B数据库中,并且备份A数据库和B数据库是交叉进行的。创建如下代码:

public class DBTools {
    volatile private boolean preVIsA = false;

    synchronized public void backupA() {
        try {
            while (preVIsA == true) {
                this.wait();
            }
            for(int i=0;i<5;i++){
                System.out.println("★★★★★");
            }
            preVIsA = true;
            notifyAll();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    synchronized public void backupB(){
        try{
            while(preVIsA==false){
                this.wait();
            }
            for( int i=0;i<5;i++){
                System.out.println("☆☆☆☆☆");
            }
            preVIsA = false;
            notifyAll();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}
public class BackupA extends Thread{
    private DBTools dbTools;

    public BackupA(DBTools dbTools){
        this.dbTools = dbTools;
    }

    @Override
    public void run() {
        dbTools.backupA();
    }
}
public class BackupB extends Thread{
    private DBTools dbTools;

    public BackupB(DBTools dbTools){
        this.dbTools = dbTools;
    }

    @Override
    public void run() {
        dbTools.backupB();
    }
}
public class Run {
    public static void main(String[] args) {
        DBTools dbTools = new DBTools();
        for(int i=0;i<20;i++){
            BackupB backupB = new BackupB(dbTools);
            backupB.start();
            BackupA backupA = new BackupA(dbTools);
            backupA.start();
        }
    }
}

执行结果如下:
enter description here
执行的结果是交替运行的。
交替打印的原理是使用下面的代码作为标记:

volatile private boolean preVIsA = false;

实现A和B线程交替执行的效果。

1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    欢迎关注个人微信号
      欢迎关注微信账号:在路上的coder .每天一篇java相关或互联网相关的文章




    个人资料
    • 访问:782976次
    • 积分:2612
    • 等级:
    • 排名:第14106名
    • 原创:85篇
    • 转载:4篇
    • 译文:0篇
    • 评论:18条
    资源分享地址
    个人博客地址
    博客专栏
    最新评论