【并发编程】线程等待、通知(wait/notify)

了解了在 Java 中如何启动一个线程,并且学习了 Thread 类的 API 以及线程的状态和锁的概念后,接下来我们学习一下线程之间的等待和通知,也就是wait/notify。我们首先思考一个问题,为什么要有wait/notify?

线程和线程之间并不是完全封闭的,可以通过共享变量进行线程间通信。但是多个线程访问同一共享变量时,如果没有同步机制,那么共享变量的值可能会出问题,并不是真正的结果,或者说不是准确的值,出现了运算错误,而我们的wait/notify就是为了解决这一问题的而出现的。

wait/notify 概念

当前线程启动后,调用对象的wait()方法,当前线程释放对象锁,从运行态进入阻塞态,然后进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout)timeout时间到自动唤醒,再继续进入运行态,继续执行。

还有notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。

线程wait

值得注意的是这两个方法不在Thread对象中,而是在Object中,也就是说所有的java类继承了这两个方法,而java类继承这两个方法的原因是java同步操作的需要,说着这里我们必须对线程同步有所了解。

线程同步

什么是线程同步呢?我们先看看什么是异步,异步其实就是指多个线程同时执行。但在多个线程同时执行的过程中,可能会访问共享资源,此时我们希望确保多个线程在同一时间只能有一个线程访问,此时就称之为线程同步,最简单的就是加锁来实现。

使用实例:

两位老师给学生留下抄单词的作业,最多留下十份作业,两个学生从十分作业中各自拿出一份抄写,当作业不够十份后,老师继续留作业,指导够十份为止,然后等待,不够十份继续留作业。学生一直抄写作业,当把老师留下的作业抄写完毕,等待,然后老师留作业后继续抄写。

public class Student extends Thread {
    private String name;
    private LinkedList<Task> tasks;

    public Student(String name, LinkedList<Task> tasks) {
        //调用Thread构造方法,设置threadName
        super(name);
        this.name = name;
        this.tasks = tasks;
    }

    public void copyWord() throws InterruptedException {
        String threadName = Thread.currentThread().getName();

        while (true) {
            Task task = null;

            synchronized (tasks) {
                if (tasks.size() > 0) {
                    task = tasks.removeFirst();
                    sleep(100);
                    tasks.notifyAll();
                } else {
                    System.out.println(threadName+"开始等待");
                    tasks.wait();
                    System.out.println("学生线程 "+threadName + "线程-" + name + "等待结束");
                }
            }

            if (task != null) {
                for (int i = 1; i <= task.getLeftCopyCount(); i++) {
                    System.out.println(threadName + "线程-" + name + "抄写" + task.getWordToCopy() + "。已经抄写了" + i + "次");
                }
            }
        }
    }

    //重写run方法,完成任务。
    @Override
    public void run() {
        try {
            copyWord();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 

public class Task {
    private int leftCopyCount;

    public int getLeftCopyCount() {
        return leftCopyCount;
    }

    public Task(int leftCopyCount, String wordToCopy) {
        this.leftCopyCount = leftCopyCount;
        this.wordToCopy = wordToCopy;
    }

    public void setLeftCopyCount(int leftCopyCount) {
        this.leftCopyCount = leftCopyCount;
    }

    public String getWordToCopy() {
        return wordToCopy;
    }

    public void setWordToCopy(String wordToCopy) {
        this.wordToCopy = wordToCopy;
    }

    private String wordToCopy;
}

 

public class Teacher extends Thread {
    private String name;
    private List<String> punishWords = Arrays.asList("internationalization", "hedgehog", "penicillin", "oasis", "nirvana", "miserable");
    private LinkedList<Task> tasks;
    private int MAX = 10;

    public Teacher(String name, LinkedList<Task> tasks) {
        //调用Thread构造方法,设置threadName
        super(name);
        this.name = name;
        this.tasks = tasks;
    }

    public void arrangePunishment() throws InterruptedException {
        String threadName = Thread.currentThread().getName();

        while (true) {
            synchronized (tasks) {
                if (tasks.size() < MAX) {
                    Task task = new Task(new Random().nextInt(3) + 1, getPunishedWord());
                    tasks.addLast(task);
                    System.out.println(threadName + "留了作业,抄写" + task.getWordToCopy() + " " + task.getLeftCopyCount() + "次");
                    tasks.notifyAll();
                } else {
                    System.out.println(threadName+"开始等待");
                    tasks.wait();
                    System.out.println("teacher线程 " + threadName + "线程-" + name + "等待结束");
                }
            }
        }
    }

    //重写run方法,完成任务。
    @Override
    public void run() {
        try {
            arrangePunishment();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private String getPunishedWord() {
        return punishWords.get(new Random().nextInt(punishWords.size()));
    }
}
public class WaitNotifyClient {
    public static void main(String[] args) {
        LinkedList<Task> tasks = new LinkedList<>();

        Student xiaoming = new Student("小明", tasks);
        xiaoming.start();

        Student xiaowang = new Student("小王", tasks);
        xiaowang.start();

        Teacher lilaoshi = new Teacher("李老师", tasks);
        lilaoshi.start();

        Teacher zhanglaoshi = new Teacher("张老师", tasks);
        zhanglaoshi.start();
    }
}

运行结果如下:

 

 使用中的注意事项:

1.wait是阻塞当前进程,必须获得锁才能wait,一般配置synchronized关键字使用。反之,如果线程能执行wait,notify/notify等方法,说明线程一定是获得了锁的。

2.当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。只有当 notify/notifyAll() 被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁。

4、wait() 需要被try catch包围,以便发生异常中断也可以使wait等待的线程唤醒。

5、notify 和wait 的顺序不能错,如果A线程先执行notify方法,B线程在执行wait方法,那么B线程是无法被唤醒的。

6、notify 和 notifyAll的区别

notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll 方法。比如在生产者-消费者里面的使用,每次都需要唤醒所有的消费者或是生产者,以判断程序是否可以继续往下执行。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值