为了账号安全,请及时绑定邮箱和手机立即绑定

线程间的通信--等待唤醒机制

标签:
Java

1.多个线程操作相同的资源,但是操作动作不同,所以存在安全问题
例如:

public class Test {    public static void main(String[] args) {        Resource r = new Resource();        Input in = new Input(r);        Output out = new Output(r);        Thread tin = new Thread(in);        Thread tout = new Thread(out);        tin.start();        tout.start();    }}/** * 两个线程共同的资源 * @author WangShuang * */class Resource{    private String name;    private String sex;    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public String getSex() {        return sex;    }    public void setSex(String sex) {        this.sex = sex;    }}/** * 存资源的线程 * @author WangShuang * */class Input implements Runnable {     private Resource resource;    public Input(Resource resource) {        this.resource=resource;    }    @Override    public void run() {        int x =0;        while(true){            synchronized (new Object()) {                if(x==0){                    resource.setName("张三");                    resource.setSex("男");                }else{                    resource.setName("lili");                    resource.setSex("女女女");                }                x=(x+1)%2;            }        }    }}/** *  * @author 取资源的线程 * */class Output implements  Runnable {    private Resource resource;    public Output(Resource resource) {        this.resource=resource;    }    @Override    public void run() {        while(true){            synchronized (new Object()) {                System.out.println(resource.getName()+"..."+resource.getSex());            }        }    }}

运行结果:
张三...男
张三...女女女
张三...男
张三...男
张三...女女女
lili...男

发生上述问题的原因:当output取线程时,output还没取完,例如只get到了name张三,cpu的执行权就被input夺走,执行了两个setlili 女,当output再次取线程是sex已经变成了女,所以出

现了 张三。。。女的现象
添加了同步也不管用,那么就应该思考同步的前提 是不是两个或两个以上的线程操作共享资源,是不是同一个锁对象,很明显没有满足

2.现在更改代码用同一个锁

public class Test {    public static void main(String[] args) {        Resource r = new Resource();        Input in = new Input(r);        Output out = new Output(r);        Thread tin = new Thread(in);        Thread tout = new Thread(out);        tin.start();        tout.start();    }}/** * 两个线程共同的资源 * @author WangShuang * */class Resource{    private String name;    private String sex;    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public String getSex() {        return sex;    }    public void setSex(String sex) {        this.sex = sex;    }}/** * 存资源的线程 * @author WangShuang * */class Input implements Runnable {     private Resource resource;    public Input(Resource resource) {        this.resource=resource;    }    @Override    public void run() {        int x =0;        while(true){            synchronized (resource) {                if(x==0){                    resource.setName("张三");                    resource.setSex("男");                }else{                    resource.setName("lili");                    resource.setSex("女女女");                }                x=(x+1)%2;            }        }    }}/** *  * @author 取资源的线程 * */class Output implements  Runnable {    private Resource resource;    public Output(Resource resource) {        this.resource=resource;    }    @Override    public void run() {        while(true){            synchronized (resource) {                System.out.println(resource.getName()+"..."+resource.getSex());            }        }    }}

运行结果
张三...男
张三...男
张三...男
张三...男
张三...男
张三...男
张三...男
张三...男
张三...男
张三...男
lili...女女女
lili...女女女
lili...女女女
lili...女女女
lili...女女女
lili...女女女
lili...女女女
lili...女女女
lili...女女女
lili...女女女
虽然已经解决了张三。。。女和lili...男的问题,但是出现的大片的张三...男和大片的lili...女,这是为什么呢

当tin线程获得cpu的执行权后,执行了setName和setSext方法,cpu的执行权依然在tin线程手里,又一次执行了setName和setSext方法,覆盖了之前的setName和setSex方法,之后的同理。。。。。,那么又该如何实现存一个取一个呢?java的多线程中存在等待唤醒机制,代码如下

public class Test1 {    public static void main(String[] args) {        Resource1 r = new Resource1();        Input1 input = new Input1(r);        Output1 output = new Output1(r);        new Thread(input).start();        new Thread(input).start();        new Thread(output).start();        new Thread(output).start();    }}/** * 两个线程共同的资源 * @author WangShuang * */class Resource1{    private String name;    private String sex;    private int count;    private boolean flag;//添加一个标记用来表示Resource中的资源是否为空(Input以后代表存入不为空,Output以后代表取出为空)    public  String getOutput() {        System.out.println(Thread.currentThread().getName()+"消费了一个"+sex+"---------------"+name);        return name+"---"+sex;    }    public  void setInput(String name,String sex) {        this.name = name+count++;        this.sex = sex;        System.out.println(Thread.currentThread().getName()+"生产了一个"+this.sex+"---"+this.name);    }    public boolean isFlag() {        return flag;    }    public void setFlag(boolean flag) {        this.flag = flag;    }}/** * 存资源的线程 * @author WangShuang * */class Input1 implements Runnable {     private Resource1 resource;    public Input1(Resource1 resource) {        this.resource=resource;    }    @Override    public void run() {        int x =0;        while(true){            synchronized (resource) {                if(resource.isFlag()){//如果flag是真,代码资源库中的资源还没有被取走,此时该线程应该放弃cpu的执行权,并把另一个线程叫醒                    try {resource.wait();} catch (InterruptedException e) {e.printStackTrace();}                }                try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}//此处的线程等待是为了方便看运行结果                if(x==0){                    resource.setInput("张三","男");                }else{                    resource.setInput("lili","女");                }                x=(x+1)%2;                resource.setFlag(true);                resource.notify();            }        }    }}/** *  * @author 取资源的线程 * */class Output1 implements  Runnable {    private Resource1 resource;    public Output1(Resource1 resource) {        this.resource=resource;    }    @Override    public void run() {        while(true){            synchronized (resource) {                if(!resource.isFlag()){//如果flag是真,代码资源库中的资源还没有被取走,此时该线程应该放弃cpu的执行权,并把另一个线程叫醒                    try {resource.wait();} catch (InterruptedException e) {e.printStackTrace();}                }                try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}//此处的线程等待是为了方便看运行结果                resource.getOutput();                resource.setFlag(false);                resource.notify();            }        }    }}

运行结果如下:
Thread-0生产了一个男---张三0
Thread-3消费了一个男---------------张三0
Thread-1生产了一个男---张三1
Thread-2消费了一个男---------------张三1
Thread-1生产了一个女---lili2
Thread-3消费了一个女---------------lili2
Thread-0生产了一个女---lili3
Thread-3消费了一个女---------------lili3
Thread-0生产了一个男---张三4
Thread-3消费了一个男---------------张三4
Thread-1生产了一个男---张三5
Thread-2消费了一个男---------------张三5
Thread-1生产了一个女---lili6
Thread-3消费了一个女---------------lili6
Thread-0生产了一个女---lili7
Thread-3消费了一个女---------------lili7
Thread-0生产了一个男---张三8
Thread-3消费了一个男---------------张三8
Thread-0生产了一个女---lili9
Thread-3消费了一个女---------------lili9
Thread-0生产了一个男---张三10
Thread-2消费了一个男---------------张三10
Thread-0生产了一个女---lili11
Thread-1生产了一个男---张三12
Thread-0生产了一个男---张三13
Thread-1生产了一个女---lili14
Thread-0生产了一个女---lili15
Thread-2消费了一个女---------------lili15
Thread-3消费了一个女---------------lili15
Thread-1生产了一个男---张三16
Thread-2消费了一个男---------------张三16
Thread-3消费了一个男---------------张三16

Thread-1生产了一个女---lili17

出现了生产一个,消费2个的状态,那么这是为什么呢?

首先明白一件事:不具备执行资格的线程存在于内存中的线程池中,唤醒的线程的顺序,是谁先被等待,谁先被唤醒

当Thread-0获得cpu的执行权时,先判断资源是否为空,是开始生产资源,然后把判断资源是否为空的标记flag,设置为true,唤醒一个线程,Thread-0继续拥有cpu的执行权,先判断资源是否为空,不是空,wait()等待。然后Thread-1获得cpu的执行权,先判断资源是否为空,不是空,wait()等待。然后Thread-2获得cpu的执行权,先判断资源是否为空,不是空,取出资源,然后flag为false,唤醒一个线程,然后Thread-3开始执行,先判断资源是否为空,是空,wait()等待,然后Thread-0获得cpu的执行权,上次Thread-0失去cpu的执行权时是通过resource.wait(),接着继续运行,生产资源,设置flag为true,唤醒一个线程,Thread-1获得cpu的执行权,上次Thread-1失去cpu的执行权时是通过resource.wait(),接着继续运行,生产资源,因此产生了生产两个资源的现象,设置flag为true,唤醒一个线程,然后Thread-2获得cpu的执行权,因为上次失去cpu的执行权是在resource.wait(),所以继续执行,不用判断资源是否为空,取出资源,然后flag为false,唤醒一个线程,然后Thread-3开始执行,因为上次失去cpu的执行权是在resource.wait(),所以继续执行,不用判断资源是否为空,取出资源,因此产生了消费两个相同资源的现象
注意现在的线程顺序是我自己本人想象出来的cpu的执行顺,cpu的执行顺序是随机的,所以各种现象都能解释的通,例如生产了两个,消费了一个,生产一个消费两个,生产两个不一样的,消费2个一样的(上述解释)等等,那么这个问题该如何解决呢?

产生上述问题的原因是因为,没有判断标记,那么
把判断资源是否为空的地方if(resource.getFlag())改成while(resource.getFlag()),这样就可以循环的判断标记了,当我们再次运行程序时,会发现Thread-0,Thread-1,Thread-2,Thread-3,全部等待的状态,产生这种现象的原因,自己按照代码的执行顺序自己理一遍就明白了,那么又该怎么办呢?

唤醒时,不要只唤醒一个线程,全部都唤醒好了notifyAll()

下面的代码是最后的并且简化后的代码

public class Test {    public static void main(String[] args) {        Resource r = new Resource();        Input input = new Input(r);        Output output = new Output(r);        new Thread(input).start();        new Thread(input).start();        new Thread(output).start();        new Thread(output).start();    }}/** * 两个线程共同的资源 * @author WangShuang * */class Resource{    private String name;    private String sex;    private int count;    private boolean flag;//添加一个标记用来表示Resource中的资源是否为空(Input以后代表存入不为空,Output以后代表取出为空)    public synchronized String getOutput() {        while(flag){//如果flag是真,代码资源库中的资源还没有被取走,此时该线程应该放弃cpu的执行权,并把另一个线程叫醒            try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}        }        System.out.println(Thread.currentThread().getName()+"消费了一个"+sex+"---------------"+name);        flag=true;        this.notifyAll();        return name+"---"+sex;    }    public synchronized void setInput(String name,String sex) {        while(!flag){//如果flag是假,代码资源库中的资源已经被取走,此时该线程应该放弃cpu的执行权,并把另一个线程叫醒            try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}        }        this.name = name+count++;        this.sex = sex;        System.out.println(Thread.currentThread().getName()+"生产了一个"+this.sex+"---"+this.name);        flag=false;        this.notifyAll();    }}/** * 存资源的线程 * @author WangShuang * */class Input implements Runnable {     private Resource resource;    public Input(Resource resource) {        this.resource=resource;    }    @Override    public void run() {        int x =0;        while(true){                if(x==0){                    resource.setInput("张三","男");                }else{                    resource.setInput("lili","女");                }                x=(x+1)%2;        }    }}/** *  * @author 取资源的线程 * */class Output implements  Runnable {    private Resource resource;    public Output(Resource resource) {        this.resource=resource;    }    @Override    public void run() {        while(true){                resource.getOutput();        }    }}

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消