多线程通信:
其实就是多个线程操作同一个资源,但操作动作不同。
1、安全问题
即由于非同步时,引起的安全问题,如下代码
public class Factory
{
private int num;
private int value;
private boolean flag=true;
public void setMessage(int num,int value)
{
try{Thread.sleep(100);}catch(Exception e){}
//while(!flag)
//try{wait();}catch(Exception e){}
this.num=num;
this.value=value;
System.out.println(Thread.currentThread().getName()+"......."+"生产"+".."+this.num);
flag=false;
//notifyAll();
}
public void getMessage()
{
try{Thread.sleep(100);}catch(Exception e){}
//while(flag)
//try{wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"......."+"消费"+"......."+this.num);
flag=true;
//notifyAll();
}
}
public class Producer implements Runnable
{
private Factory r;
int num=0;
int value=5;
public Producer(Factory r)
{
this.r=r;
}
public void run()
{
while(true)
{
r.setMessage(num++,value);
}
}
}
public class Consumer implements Runnable
{
private Factory r;
public Consumer(Factory r)
{
this.r=r;
}
public void run()
{
while(true)
{
r.getMessage();
}
}
}
public class Wakeup
{
public static void main(String arg[])
{
Factory r=new Factory();
Producer r1=new Producer(r);//线程1
Consumer r2=new Consumer(r);//线程2
//Consumer r3=new Consumer(r);
Thread t1=new Thread(r1);
Thread t2=new Thread(r2);
//Thread t3=new Thread(r3);
t1.start();
t2.start();
//t3.start();
}
}
运行结果如下:
由结果可以看出,尚未生产8号时,就已经消费了8号,这不科学,所以要引用同步来解决上述问题,即在操作的方法上将其变为同步方法,修改部分代码如下:
public class Factory
{
private int num;
private int value;
private boolean flag=true;
public <span style="color:#ff0000;">synchronized</span> void setMessage(int num,int value)
{
try{Thread.sleep(100);}catch(Exception e){}
//while(!flag)
//try{wait();}catch(Exception e){}
this.num=num;
this.value=value;
System.out.println(Thread.currentThread().getName()+"......."+"生产"+".."+this.num);
flag=false;
//notifyAll();
}
public <span style="color:#ff0000;">synchronized</span> void getMessage()
{
try{Thread.sleep(100);}catch(Exception e){}
//while(flag)
//try{wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"......."+"消费"+"......."+this.num);
flag=true;
//notifyAll();
}
}
结果如下:
这样是没问题了,可是也不能一直消费10啊,我想要生产一个消费一个
2、等待唤醒
wait(); 等待,当线程调用了wait()方法时,它会释放掉对象的锁。
notify(); 一般唤醒线程池中同一把锁的第一个等待线程
notifyAll(); 唤醒线程池中同一把锁所有线程
都使用在同步中,因为要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才有锁;
为什么这些操作线程的方法要定义在Object类中呢?
因为这些方法在操作同步中线程时,都必须要标识它们所操作的线程只有锁,只有同一个锁上的被等
待线程,可以被同一个锁上的notify唤醒,不可以对不同锁上的线程进行唤醒;
也就是说,等待和唤醒必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法定义
Object类中。
生产者消费者:
2.1
public class Factory
{
private int num;
private int value;
private boolean flag=true; //生产消费标识符
public synchronized void setMessage(int num,int value)
{
try{Thread.sleep(100);}catch(Exception e){}
if(!flag) //如果flag为真,表示现在还没有生产,应进行生产;如果为假,表示现在有存货,等着消费,不进行生产
try{this.wait();}catch(Exception e){}
this.num=num;
this.value=value;
System.out.println(Thread.currentThread().getName()+"......."+"生产"+".."+this.num);
flag=false;
this.notify();
}
public synchronized void getMessage()
{
try{Thread.sleep(100);}catch(Exception e){}
if(flag) <span style="font-family: Arial, Helvetica, sans-serif;">//如果flag为真,表示现在还没有消费,应进行消费;如果为假,表示现在无存货,等着生产,不进行消费</span>
try{this.wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"......."+"消费"+"......."+this.num);
flag=true;
this.notify();
}
}
运行结果如下:
可见是生产一个消费一个,具体流程如下;
开启线程t1后,主线程可能也会离开开启线程t2,如果t2掌握了cpu执行权,则进入t2的run()方法中,然后调用r.getMessage()方法,由于该方法是同步方法,如果其有执行资格,便能进入该方法中,进入后,先上锁(this锁),然后判断flag,发现是true,于是进入this.wait(),放弃执行资格;然后线程t1就进入setMessage()方法中,判断flag,发现是true,不进入this.wait(),进行设定值的操作,然后在最后将标志flag设置为假,并用this.notify()唤醒线程t2;使得线程t2从之前的this.wait()语句后开始执行,不必判断flag,进行输出打印,然后在最后将flag标志设置为true,并用this.notify()唤醒线程t1(如果线程t1被wait()的话);依次不断重复……
2.2
在上述代码的基础上,再增加一个线程t3
public class Wakeup
{
public static void main(String arg[])
{
Factory r=new Factory();
Producer r1=new Producer(r);//线程1
Consumer r2=new Consumer(r);//线程2
Consumer r3=new Consumer(r);
Thread t1=new Thread(r1);
Thread t2=new Thread(r2);
Thread t3=new Thread(r3);
t1.start();
t2.start();
t3.start();
}
}
结果运行如下:
发现一个生产有时变成了两个消费,这明显不对,其过程如下
- 假设t1获取到执行权,判断flag,为true,不需要等待,开始生产,设置flag为false,norify完后,t1可能还持有执行权,则循环回来继续执行,判断flag为false,则进入wait(),t1放弃资格;
- 此时t2、t3、t4都有可能抢到资格,假设t2抢到了,进来判断flag为false,则进入wait(),t2放弃资格;
- 此时t3、t4有可能抢到资格,假设t3抢到了,进来判断flag为false,进行消费操作,然后设置flag为true,notify完后,把t1唤醒了,假设t3还持有执行权,循环回来继续执行,判断flag为true,进入wait();
- 此时t4、t1有可能抢到资格,假设t4抢到了,进来判断flag为true,则进入wait(),t4放弃资格;
- 此时就只t1有可能抢到资格,t1进来,不用判断flag,从wait()后开始生产,然后设置flag为false,notify完后,t2被唤醒,假设t1还持有执行权,循环回来继续执行,判断flag为false,进入wait();
- 此时只有t2有资格,不用判断flag,从wait()后开始生产,然后设置flag为false,再notify。
然后我们程序就悲剧了,连续生产了两个;连续消费两个也是同理,原因就在于被唤醒后,不用判断flag,那么我们修改程序,用while代替if,让其被唤醒后依然判断flag,代码如下
public class Factory
{
private int num;
private int value;
private boolean flag=true;
public synchronized void setMessage(int num,int value)
{
try{Thread.sleep(10);}catch(Exception e){}
<span style="color:#ff0000;">while</span>(!flag)
try{this.wait();}catch(Exception e){}
this.num=num;
this.value=value;
System.out.println(Thread.currentThread().getName()+"......."+"生产"+".."+this.num);
flag=false;
this.notify();
}
public synchronized void getMessage()
{
try{Thread.sleep(100);}catch(Exception e){}
<span style="color:#ff0000;">while</span>(flag)
try{this.wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"......."+"消费"+"......."+this.num);
flag=true;
this.notify();
}
}
运行结果如下:
然后发现,倒是生产一个消费一个了,但是只运行到1就不运行了,为什么呢?死锁!!!!
即在上述第5步中,t2被唤醒了,然后因为while判断flag为false,其也进入wait中,结果t1、t2、t3、t4均处于等待状态,则相当于死锁!
解决办法:notifyAll(),全部唤醒!
代码如下:
public class Factory
{
private int num;
private int value;
private boolean flag=true;
public synchronized void setMessage(int num,int value)
{
try{Thread.sleep(10);}catch(Exception e){}
while(!flag)
try{this.wait();}catch(Exception e){}
this.num=num;
this.value=value;
System.out.println(Thread.currentThread().getName()+"......."+"生产"+".."+this.num);
flag=false;
this.notifyAll();
}
public synchronized void getMessage()
{
try{Thread.sleep(100);}catch(Exception e){}
while(flag)
try{this.wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"......."+"消费"+"......."+this.num);
flag=true;
this.notifyAll();
}
}
此时正常了!