。导读:上一个多线程的程序,我们能保证对象的设置和打印是安全的,不会出现数据错乱和出错的情况,但是我们不能很好地控制线程的执行。我们希望的是,设置了对象以后,打印线程打印,然后再设置对象,然后再打印,如此反复。
但是多线程的执行具有随机性,我们不能这样控制线程执行。
线程1
线程2
线程1
线程2
线程1
线程2
……
但是 Java 为我们提供了“等待-唤醒”机制,通过该机制,就可以实现多线程程序好像是按照上面的方式执行一样。
1、竞争资源
public class Student {
String name;
Integer age;
// 默认情况是没有数据的,如果是 true,就说明是有数据的
// 如果是 Boolean 默认情况下就没有初始值
// 看看自动拆箱、装箱的内容
// 我们约定:刚刚被 set 就赋值为 true,刚刚被 get 就赋值为 false
boolean flag;
}
2、设置对象和访问对象的线程
(1)设置对象属性的线程:如果对象的属性设置过了,就让这个线程等待,让获取对象的线程去打印属性
/**
* Created by liwei on 16/7/18.
* 既保证了数据安全,也保证了数据好看
*/
public class SetThread implements Runnable {
private Student student;
private Integer x= 0;
public SetThread(Student student){
this.student = student;
}
@Override
public void run() {
while (true){
synchronized (student){
if(student.flag){
try {
// 这个时候,这个线程释放了锁
student.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(x%2==0){
student.name = "liwei";
student.age = 29;
}else {
student.name="zhouguang";
student.age = 26;
}
x++;
// 修改标记
student.flag =true;
// 唤醒其它线程
// // TODO: 16/7/18 和 notifyAll 有什么不同
student.notify();
}
}
}
}
(2)打印对象属性的线程:如果这个线程对象的旗标显示这个设置的对象还没被打印过,就打印,如果打印过了,这个线程就挂起,等待对象属性被重新赋值。
/**
* Created by liwei on 16/7/18.
* 既保证了数据安全,也保证了数据好看
*/
public class GetThread implements Runnable {
private Student student;
public GetThread(Student student){
this.student = student;
}
@Override
public void run() {
while (true){
synchronized (student){
// 如果还没有被赋值
if(!student.flag){
try {
// 当前线程就被挂起,直到满足竞争条件以后再往下执行
// 这个时候,该线程处于等待状态,释放了锁。
// 等到将来醒过来的时候,就继续执行后面的代码
student.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("数据 => " + student.name + " --- " + student.age);
// 已经打印过了,就将标记设置为 false
student.flag = false;
// 此时,我做完了所有的事情,就通知其它线程,可以继续了
// 唤醒线程
student.notify();
}
}
}
}
3、测试代码
/**
* Created by liwei on 16/7/18.
*
* 以上的代码保障了数据的正确,但是不保证数据好看。
* 如果要数据好看,就要引入等待、唤醒机制。
* 要点:添加了一个标记
* 测试流程: 数据错落有致。
*
*/
/*
* 分析:
* 资源类:Student
* 设置学生数据:SetThread(生产者)
* 获取学生数据:GetThread(消费者)
* 测试类:StudentDemo
*
* 问题1:按照思路写代码,发现数据每次都是:null---0
* 原因:我们在每个线程中都创建了新的资源,而我们要求的时候设置和获取线程的资源应该是同一个
* 如何实现呢?
* 在外界把这个数据创建出来,通过构造方法传递给其他的类。
*
* 问题2:为了数据的效果好一些,我加入了循环和判断,给出不同的值,这个时候产生了新的问题
* A:同一个数据出现多次
* B:姓名和年龄不匹配
* 原因:
* A:同一个数据出现多次
* CPU的一点点时间片的执行权,就足够你执行很多次。
* B:姓名和年龄不匹配
* 线程运行的随机性
* 线程安全问题:
* A:是否是多线程环境 是
* B:是否有共享数据 是
* C:是否有多条语句操作共享数据 是
* 解决方案:
* 加锁。
* 注意:
* A:不同种类的线程都要加锁。
* B:不同种类的线程加的锁必须是同一把。
*
* 问题3:虽然数据安全了,但是呢,一次一大片不好看,我就想依次的一次一个输出。
* 如何实现呢?
* 通过Java提供的等待唤醒机制解决。
*
* 等待唤醒:
* Object类中提供了三个方法:
* wait():等待
* notify():唤醒单个线程
* notifyAll():唤醒所有线程
* 为什么这些方法不定义在Thread类中呢?
* 这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象。
* 所以,这些方法必须定义在Object类中。
*/
public class StudentDemo {
public static void main(String[] args) {
Student student = new Student();
GetThread gt = new GetThread(student);
SetThread st = new SetThread(student);
Thread t1 = new Thread(gt);
Thread t2 = new Thread(st);
t1.start();
t2.start();
}
}