我在写这个例子的时候,也出了很多问题。
今天头很痛,好长时间没弄出来,心情异常的烦躁。
先说下概念吧:
多线程之前通讯,不要想复杂了。他就是一个生产者 与 消费者的概念。
比如说一个生活中的例子(这是基于我的理解自己想的一个例子):
在银行办业务,需要排队。 业务员属于一个线程,办业务的人属于一个线程。
那么在程序里就相当于:业务员刚坐下开始上班,就处于一个wait状态(等待),然后业务员就对外面的人立个牌子,空闲中。这就相当于notify,就是告诉外面的人,我这里可以办业务。 然后来银行存钱的人发现,业务员立了个空闲中的牌子,然后存钱的人就从wait(等待状态变成了)开始去办业务的状态,当这个存钱的人业务办完了,就告诉柜台的业务员我办好了。那么这个过程也就是 notify ,然后业务员知道了,准备接待下一位存钱的人,业务员又处于一个wait状态了。 就这样循环下去。
所以: wait、notify就是一个是等待,一个是通知的意思。
如果不用,wait和notify会出现什么情况呢 ?
比如:一个人在存钱的时候,来了个不讲理的人,人家的钱还没存,刚要存的时候,这个不讲理的人就说,先给我存钱。这时候,银行业务员可能抬头看见了,也可能没看见,拿着钱就存了。然后第3个人要取钱,卡给了,密码输了,又来了一个人,把第3个人推出去了。说了一句,取他一个亿。 这时候是不会就乱套了 ?同样也适用于业务员,不然,来了一个很热心的业务员,说你让开,让我来给这个美女办业务,上来也不知道要取钱还是存钱,想着这个美女应该是来开户的吧。所以业务就办错了。
所以通过上面的例子:就可以知道,很多时候需要使用到wait 和 notify。而且为了不让别人插队,需要一把锁,把进去办业务的人 和 业务员都锁起来,办好了之后,再解锁。
用代码实现,没有用 wait 和 notify 也没加锁的效果:
public class Person {
private String username;
private int age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"username='" + username + '\'' +
", age=" + age +
'}';
}
}
public class WriteRunnableThread implements Runnable {
private Person person;
public WriteRunnableThread(Person person) {
this.person = person;
}
public void run() {
/**
* 定义一个局部变量。
* 当这个值为偶数时,为Person类赋值: 偶数 0
* 当这个数为奇数时,为Person类赋值: 奇数 1
*/
int data = 0;
while (true) {
if (data == 0) {
person.setUsername("偶数");
person.setAge(0);
} else {
person.setUsername("奇数");
person.setAge(1);
}
/**
* 依次改变data的值为: 偶数、奇数
*/
data = (data + 1) % 2;
}
}
}
public class ReadRunnableThread implements Runnable {
private Person person;
public ReadRunnableThread(Person person) {
this.person = person;
}
public void run() {
while (true) {
System.out.println(person.getUsername() + "," + person.getAge());
}
}
}
public class Test {
/**
* 现在要用多线程做一件事情:
* <p>
* 创建两个线程,一个线程向Person类写数据,一个线程从Person类中读数据。
*/
public static void main(String[] args) {
Person person = new Person();
WriteRunnableThread writeRunnableThread = new WriteRunnableThread(person);
ReadRunnableThread readRunnableThread = new ReadRunnableThread(person);
Thread write = new Thread(writeRunnableThread);
Thread read = new Thread(readRunnableThread);
write.start();
read.start();
/**
* 会出现的结果:
*
* 奇数,1
* 奇数,0
* 偶数,0
* 偶数,1
*
* 有没有发现,这不是我们想要的结果 ?
* 而且也会发现,拿到的数据,并不是按顺序来的。
*/
}
}
然后,再来看,使用了wait 和 notify 并且加了同一把锁的效果:
public class Person {
public String username;
public int age;
/**
* true 生产者线程等待, false 消费者线程等待
*/
public boolean flag = false;
}
public class WriteThread extends Thread {
private Person person;
public WriteThread(Person person) {
this.person = person;
}
@Override
public void run() {
/**
* 定义一个局部变量。
* 当这个值为偶数时,为Person类赋值: 偶数 0
* 当这个数为奇数时,为Person类赋值: 奇数 1
*/
int data = 0;
while (true){
synchronized (person) {
/**
* true: 等待消费
*/
if (person.flag) {
try {
person.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (data == 0) {
person.username = "偶数";
person.age = 0;
} else {
person.username = "奇数";
person.age = 1;
}
/**
* 依次改变data的值为: 偶数、奇数
*/
data = (data + 1) % 2;
person.flag = false;
person.notify();
}
}
}
}
public class ReadThread extends Thread {
public Person person;
public ReadThread(Person person) {
this.person = person;
}
@Override
public void run() {
while (true){
synchronized (person) {
if (!person.flag) {
try {
person.wait();
} catch (InterruptedException e) {
}
}
System.out.println(person.username + "," + person.age);
person.flag = true;
person.notify();
}
}
}
}
public class Test {
/**
* 现在要用多线程做一件事情:
* <p>
* 创建两个线程,一个线程向Person类写数据,一个线程从Person类中读数据。
* <p>
* 写这个例子的时候报了很多错:
* 1. notify 没有在synchronized代码块中报错。
* 2. 没有使用 synchronized 也报错了。
* 3. 没有使用对象锁,也报错了。
*
* 所以:
* 使用wait 和 notify,一定要在同步代码块中,同时存在。
* 因为wait 和 notify 在Object内中,所以一定要使用同一个对象锁。
*/
public static void main(String[] args) {
Person person = new Person();
WriteThread writeThread = new WriteThread(person);
ReadThread readThread = new ReadThread(person);
writeThread.start();
readThread.start();
}
}
上面就是使用 wait notify 加锁的代码实现。
重点:(想了解这个可以去看 多线程高级讲解五: 多线程的Lock锁,多线程同步、多线程并发的概念 )
wait 、notify只能在synchronized中使用,不能在jdk1.5的Lock锁中使用。
在Lock锁中,如果要线程之间通讯,需要使用Condition类中的await 和 signal 。
wait 与 sleep的区别是什么 ?
他们都是做休眠的。
wait必须在synchronized同步代码块中使用,只要运行到wait的代码,下面的代码就不会执行(处于等待状态),他会等待其他线程notify的唤醒才能从休眠状态改为运行状态,通过上面的例子,还可以看出wait还可以释放锁的资源。
sleep在线程中只等待,不会释放锁的资源。
本来想把上面这个例子,也实现成 银行业务员 和 客户这种关系,但是今天实在不想再看电脑屏幕了。将就着看吧。