Java实现--线程间通信问题案例

来源:互联网 发布:百度鹰眼轨迹数据 编辑:程序博客网 时间:2024/06/05 07:08

案例:给学生设置和获取姓名和年龄。

案例分析:我们把学生对象作为一个资源进行操作,利用设置线程给学生对象设置姓名和年龄,利用获取线程获取姓名和年龄。

图解
这里写图片描述

可以看出这是一个单生产单消费问题。

代码实现
Student – 被设置的资源对应的类

package com.edu_01;public class Student {    String name;    int age;}

SetThread – 设置线程

package com.edu_01;public class SetThread extends Thread{    private Student s;    private int x=0;    public SetThread(Student s){        this.s=s;    }    @Override    public void run() {        while (true) {            synchronized (s) {                if (x%2==0) {                    s.name="张三";                    s.age=22;                } else {                    s.name="李四";                    s.age=23;                }                x++;            }        }    }}

GetThread – 获取线程

package com.edu_01;public class GetThread extends Thread{    private Student s;    public GetThread(Student s){        this.s=s;    }    @Override    public void run() {        while (true) {            synchronized (s) {                System.out.println(s.name+":"+s.age);            }        }    }}

StudnetDemo – 测试类

package com.edu_01;public class StudentDemo {    public static void main(String[] args) {        //创建学生对象        Student s = new Student();        //创建设置线程和获取线程并开启        SetThread st = new SetThread(s);        GetThread gt = new GetThread(s);        st.start();        gt.start();    }}

运行结果:
这里写图片描述

这个案例实现过程中遇到的问题
(1)打印null–0:
原因:设置和获取不是同一个对象,在设置线程和获取线程中new了两次,所以创建了两个学生对象。
解决:当在不同的类中,需要使用同一个数据的时候,可以考虑将数据利用构造方法进行传参

(2)出现数据错乱,线程安全问题:
原因:是多线程情况;存在共享数据;存在多条语句对共享数据进行操作。所以存在线程安全问题。
解决:同步代码块,加锁。设置线程和获取线程,加的锁必须是同一个。

(3)打印的结果不够和谐:
原因:上面那个代码开启测试类后,会开启两个线程,一个是设置线程,一个是获取线程,然而我们并没有决定让谁抢到CPU的执行权。


对于第三个问题,我们可以使用等待唤醒机制来改进,实现礼让效果。
等待唤醒机制用到的几个方法

wait()//让该线程处于等待状态notify()//唤醒等待的线程,如果没有线程在等待,则不起作用notifyAll()//唤醒所有等待的线程

注意:这几个方法都只能通过锁对象调用,只有在同步的时候,这几个方法才有效果。

改进后的代码
Student – 被设置的资源对应的类

package com.edu_02;public class Student {    String name;    int age;    boolean flag;//用来判断对象中是否有数据}

SetThread – 设置线程

package com.edu_02;public class SetThread implements Runnable{    private Student s;    private int x=0;    public SetThread(Student s){        this.s=s;    }    @Override    public void run() {        while (true) {            synchronized (s) {                //判断此时对象中有没有数据                if (s.flag) {                    try {                        s.wait();//设置线程等待,同时释放锁s                    } catch (InterruptedException e) {                        // TODO Auto-generated catch block                        e.printStackTrace();                    }                }                if (x%2==0) {                    s.name="张三";                    s.age=22;                } else {                    s.name="李四";                    s.age=33;                }                x++;                s.flag=true;//此时对象中有数据了                s.notify();//唤醒正在等待的线程,如果没有线程等待则没有任何效果            }        }    }}

GetThread – 获取线程

package com.edu_02;public class GetThread implements Runnable{    private Student s;    public GetThread(Student s){        this.s=s;    }    @Override    public void run() {        while (true) {            synchronized (s) {                //判断对象中是否有数据                if (!s.flag) {                    try {                        s.wait();//获取线程等待,释放锁s                    } catch (InterruptedException e) {                        // TODO Auto-generated catch block                        e.printStackTrace();                    }                }                System.out.println(s.name+":"+s.age);                s.flag=false;//当获取线程从对象中获取数据后,我们默认对象中没有数据了,此时我们应该让设置线程继续给学生对象设置学生信息                s.notify();            }        }    }}

StudnetDemo – 测试类

package com.edu_02;public class StudentDemo {    public static void main(String[] args) {        //创建学生对象        Student s = new Student();        //创建设置线程和获取线程        SetThread st = new SetThread(s);        GetThread gt = new GetThread(s);        Thread t1 = new Thread(st);        Thread t2 = new Thread(gt);        //开启线程        t1.start();        t2.start();    }}

运行结果:
这里写图片描述


对上面代码继续优化
(1)私有化Student类的成员变量
(2)在类的内部提供设置和获取的同步方法

代码实现
Student – 被设置的资源对应的类

package com.edu_03;public class Student {    //私有化成员变量    private String name;    private int age;    private boolean flag;//用来标记学生对象中是否有信息    //提供公共方法设置信息    public synchronized void setInfo(String name,int age){        if (this.flag) {            try {                this.wait();//等待            } catch (InterruptedException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }        }        //如果学生对象中没有信息,则设置信息        this.name=name;        this.age=age;        //更改标记,唤醒线程        this.flag=true;        this.notify();    }    //提供公共方法获取信息    public synchronized void getInfo(){        if (!this.flag) {            try {                this.wait();            } catch (InterruptedException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }        }        //如果学生对象中有信息,则获取信息        System.out.println(this.name+":"+this.age);        //更改标记,唤醒线程        this.flag=false;        this.notify();    }}

SetThread – 设置线程

package com.edu_03;public class SetThread implements Runnable{    private Student s;    private int x=0;    public SetThread(Student s){        this.s=s;    }    @Override    public void run() {        while (true) {            if (x%2==0) {                s.setInfo("张三",22);            } else {                s.setInfo("李四",33);            }            x++;        }    }}

GetThread – 获取线程

package com.edu_01;public class GetThread implements Runnable{    private Student s;    public GetThread(Student s){        this.s=s;    }    @Override    public void run() {        while (true) {            s.getInfo();        }    }}

StudnetDemo – 测试类

package com.edu_03;public class StudentDemo {    public static void main(String[] args) {        //设置学生对象        Student s = new Student();        //创建设置线程和获取线程        SetThread st = new SetThread(s);        GetThread gt = new GetThread(s);        Thread t1 = new Thread(st);        Thread t2 = new Thread(gt);        //开启线程        t1.start();        t2.start();    }}

运行结果:
这里写图片描述

原创粉丝点击