多线程 —— 代码安全以后,对线程调度的控制(等待-唤醒机制)

来源:互联网 发布:淘宝的流量钱包自动领 编辑:程序博客网 时间:2024/06/12 01:53

。导读:上一个多线程的程序,我们能保证对象的设置和打印是安全的,不会出现数据错乱和出错的情况,但是我们不能很好地控制线程的执行。我们希望的是,设置了对象以后,打印线程打印,然后再设置对象,然后再打印,如此反复。

但是多线程的执行具有随机性,我们不能这样控制线程执行。
线程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();    }}
0 0
原创粉丝点击