目录

为什么需要通信:

剩余的问题:

输出结果:

通信有关的方法:

wait()

notify()

notifyAll()

使用条件

生产者与消费者问题:

为什么要有一个Clerk类?

为什么要分两个不同的子类实现Runnable?


为什么需要通信:

我们在买车票时,只需要车票按照顺序减少即可,但是在与朋友一对一单挑牌技时,需要轮流摸牌

此时,不仅需要同步(不能同时抢同一张牌),也需要线程的通信(轮流摸牌)

可以用notify(), wait()方法来实现。notify方法用来唤醒其他线程(提醒他人摸牌)。wait线程用来睡眠并放弃锁(摸完后等待)

剩余的问题:

用这个方法轮流执行只能保证两个人轮流执行,当人数大于2时,在notify()时不一定能再下一次执行时,选择正确的玩家

public class Main {
    public static void main(String[] args) {
        MyThread mt = new MyThread(10);
        Thread thread1 = new Thread(mt);
        Thread thread2 = new Thread(mt);
        thread1.setName("玩家一");
        thread2.setName("玩家二");
        thread1.start();
        thread2.start();
    }
}

class MyThread implements Runnable{


    private int max;

    public MyThread(int max) {
        this.max = max;
    }

    @Override
    public void run() {
        while (true){
            synchronized (this){
                if(max>0){
                    System.out.println("轮到" + Thread.currentThread().getName() + "摸牌" + " 还剩余 "+ --max + "张牌");
                    notify();//提醒下一名玩家摸牌
                }else{
                    System.out.println("快点啊,我等得花都谢了");
                    break;
                }
                try {
                    wait();//提醒完后等他摸完,他摸完后也会提醒自己
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }
}

输出结果:

轮到玩家一摸牌 还剩余 9张牌

轮到玩家二摸牌 还剩余 8张牌

轮到玩家一摸牌 还剩余 7张牌

轮到玩家二摸牌 还剩余 6张牌

轮到玩家一摸牌 还剩余 5张牌

轮到玩家二摸牌 还剩余 4张牌

轮到玩家一摸牌 还剩余 3张牌

轮到玩家二摸牌 还剩余 2张牌

轮到玩家一摸牌 还剩余 1张牌

轮到玩家二摸牌 还剩余 0张牌

快点啊,我等得花都谢了

通信有关的方法:

wait()

wait():放弃锁并睡眠。

在当前线程中调用方法: 对象名.wait()

使当前线程进入等待(某对象)状态 ,直到另一线程对该对象发出 notify (或notifyAll) 为止。

调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)

调用此方法后,当前线程将释放对象监控权 ,然后进入等待

在当前线程被notify后,要重新获得监控权,然后从断点处继续代码的执行。

notify()

在当前线程中调用方法: 对象名.notify()

功能:唤醒等待该对象监控权的一个线程。

调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)

notifyAll()

在当前线程中调用方法: 对象名.notifyAll ()

功能:唤醒等待该对象监控权的所有线程。

调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)

使用条件

这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常。

因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object类中声明

生产者与消费者问题:

public class Main {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Thread thread1 = new Thread(new Consumer(clerk));
        Thread thread2 = new Thread(new Producer(clerk));
        thread1.setName("消费者");
        thread2.setName("生产者");
        thread1.start();
        thread2.start();
    }
}

class Clerk {
    private int sodaNum = 0;
    public synchronized  void produce(){
        if(sodaNum>=20){
            try {
                wait();
            }catch (Exception e){
                e.printStackTrace();
            }
        }else {
            sodaNum++;
            System.out.println(Thread.currentThread().getName() + "生产了一瓶汽水,现存" + sodaNum + "瓶");
            notify();
        }
    }

    public synchronized void consume(){
        if(sodaNum <= 0){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            sodaNum--;
            System.out.println("消费者消费了一瓶,还剩"+sodaNum+"瓶");
            notify();
        }
    }
}

class Producer implements Runnable{
    private Clerk clerk;

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep((int)Math.random()*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.produce();
        }
    }
}

class Consumer implements Runnable{
    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }
    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep((int)Math.random()*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.consume();
        }
    }
}

为什么要有一个Clerk类?

因为生产者生产汽水后要提醒notify()消费者——现在可以消费了。

同理,消费者消费完后需要提醒notify()生产者——现在可以生产了。

所以,此时是生产者与消费者之间的通信,需要共用同一把锁,(同一个clerk对象)

为什么要分两个不同的子类实现Runnable?

因为,虽然消费者与生产者共用同一把锁,但是,需要两个不同的线程并发进行

换句话说Thread的构造器中需要传入两个不同的Runnble参数,不仅仅是对象不同,对象的行为(类中定义)也不同。

所以需要两个不同的类(生产者/消费者)共同操作同一把锁(Clerk)