线程间的通信--等待唤醒机制
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();
}
}
}
以上是关于线程间的通信--等待唤醒机制的主要内容,如果未能解决你的问题,请参考以下文章