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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程间的通信--等待唤醒机制相关的知识,希望对你有一定的参考价值。

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();
        }
    }
}

以上是关于线程间的通信--等待唤醒机制的主要内容,如果未能解决你的问题,请参考以下文章

线程系列三线程的等待与唤醒机制

java-等待唤醒机制(线程中的通信)-线程池

线程__线程间的通信

线程间通信——等待唤醒机制

多线程间的通讯之等待唤醒机制

等待唤醒机制,UDP通信和TCP通信