day12_多线程间通信

 

多线程通信引入:

InOut2  

class Resource
{
     String name;
     String sex;
     boolean flag=true;
}
class Input implements Runnable
{
  private Resource r;
  Input(Resource r)
  {
    this.r=r;
  }
  public void run()
  {  
    int x=0;
    while(true)
      {

               synchronized(r)//也可以用类文件对象Input.class/Output.class
               {
                    if(r.flag)
                   {
                      if(x==0)
                        {
                         r.name="Mike";
                         r.sex="men";
                         r.flag=false;
                        }
                        else
                         {
                             r.name="Lili";
                             r.sex="女女女女女女";
                             r.flag=false;
                        
                         }
                    x=(x+1)%2;
                   }
                  
               }
           
           }
      }
}

class Output implements Runnable
{
   private Resource r;
   Output(Resource r)
   {
     this.r=r;
   
   }
   public void run()
   {    while(true)
       {
          
               synchronized(r)
               {
                
                  if(!r.flag)
                  {
                    
                     try{Thread.sleep(100);}catch(Exception e){}
                     System.out.println(r.name+"----"+r.sex+"----"+r.flag);
                     r.flag=true;
                    
                  }
                  
               }
           
       }
   }
}
class InOutDemo
{
    public static void main(String[] args)
    {
      Resource r=new Resource();
      new Thread(new Input(r)).start();
      new Thread(new Output(r)).start();
    }
}
/*
对输出结果分析:
  可能出现:
 一个线程在执行完:
     r.name="Mike";
     r.sex="men";
     下次执行else内容时
     r.name="Lili";
  执行到此,cpu切换到另一线程:
     输出r.name="Lili",r.sex="men";
*/

/*
解决以上问题采用同步代码块:
对输出结果分析:
为什么没有输出Mike--men,Lili--"女女女女"交替?
 这是因为,当一个线程执行完赋值动作后,
  Cpu可能切换到另一个线程进行循环打印
*/

InOut

InOutNormal

 

多线程通信-等待唤醒机制

/*
等待唤醒机制
比喻:(有助于理解,在毕老师基础上加了点,可能比喻有偏差,仅供参考)

一种"冰棍化了"了游戏,一群小朋友来到一个院子(锁),一个主角(CPU)追一群小朋友(线程),当
其中一人被追上(执行)时,他喊"冰棍"(wait),主角不能在抓这个人了.
而其他人在躲闪过程中可以救(notify)这个喊了冰棍的人.

(API:这翻译真心拗口,英语不好吃亏!)
wait():
前提:当前线程必须拥有此 对象监视器(锁)。
该线程发布(放弃)对此监视器(锁)的所有权并等待,直到其他线程通过调用 notify 方法,
或 notifyAll 方法通知 在此对象的监视器上等待 的线程醒来.
然后该线程将等到 重新获得对监视器的所有权后 才能继续执行。
*/
//需求:为了达到赋值一次,输出一次.
class Resource
{
     String name;
     String sex;
     boolean flag=false;
}


class Input  implements Runnable 
{
  private Resource r;
  Input(Resource r)
  {
    this.r=r;
  }
  public void run()
  {  
    int x=0;
    while(true)
      {

               synchronized(r)//也可以用类文件对象Input.class/Output.class
               {
                     if(r.flag)
                      try{r.wait();}catch(Exception e){}//当已赋值完成,该线程等待
                                                      //wait()方法是Object中的方法,该方法会抛出异常,不能在run上抛出                             
                      if(x==0)                       
                        {
                         r.name="Mike";
                         r.sex="men";
                         
                        }
                        else
                         {
                           r.name="Lili";
                           r.sex="女女女女女女";
                            
                         }
                     r.flag=true;
                 
                     r.notify();//唤醒另一个线程去取
                  
           
                     
                     x=(x+1)%2;
                }
                  
      }
           
  }
}


class Output implements Runnable
{
   private Resource r;
   Output(Resource r)
   {
     this.r=r;
   
   }
   public void run()
   {    while(true)
       {
          
               synchronized(r)
               {                              
                     if(!r.flag)
                        try{r.wait();}catch(Exception e){}
                     
                     System.out.println(r.name+"----"+r.sex+"----"+r.flag);
                     
                     r.flag=false;
                    r.notify();
                     
                                          
                 
               }
           
       }
   }
}
class InOutDemo2
{
    public static void main(String[] args)
    {
      Resource r=new Resource();
      new Thread(new Input(r)).start();
      new Thread(new Output(r)).start();
    }
}



/*
注意:
关于r.wait():必须标识出wait()操作的 线程 所持有的锁(r)
因为可能有同步嵌套(不同的锁)

r.notify()唤醒的是: r锁上的 等待线程


wait(),notify(),notifyAll()
    都使用在同步中,因为要对持有监视器(锁)的线程操作
    所以要使用在同步中,因为只有同步才具有锁

Q3.为什么wait()方法定义在Object类中?
   因为 锁可以是任意对象,任意对象都能调用的方法->定义在Object中

同一个锁上等待的线程,只能被同一个锁上线程notify
不可以对不同锁中的线程进行唤醒.
*/

两个线程交替执行

对以上代码简单优化(同步函数)

class Resource   
{
    private String name,sex;
    private boolean flag=false;
    public synchronized void setValue(String name,String sex)
    {
        if(flag)
          try{this.wait();}catch(Exception e){}//this可以省略
        this.name=name;
        this.sex=sex;
        flag=true;
        this.notify();
    }
    public synchronized void printValue()
    {
        if(!flag)
           try{this.wait();}catch(Exception e){}
        System.out.println(this.name+"----"+this.sex);
        flag=false;
        this.notify();
    }
}
class Input implements Runnable
{
 private Resource r;
 Input(Resource r)
 {
   this.r=r;
 
 }
 public void run()
 {
     int x=0;
     while (true)
     {
        
        if(x==0)
          r.setValue("mike","man");
        else
           r.setValue("Lili","女女女");
        x=(x+1)%2;
     }

 
 }

}
class Output implements Runnable
{
 private Resource r;
 Output(Resource r)
 {
   this.r=r;
 
 }
 public void run()
 {
     int x=0;
     while (true)
     {
      try{Thread.sleep(100);}catch(Exception e){}
      r.printValue();
     }

 
 }

}
class InOutDemo3
{
    public static void main(String[] args)
    {
      Resource r=new Resource();
      new Thread(new Input(r)).start();
      new Thread(new Input(r)).start();
    
    }
}

生产者-消费者(两个线程生产,两个线程消费)

class Resource   
{
    private int count=1;
    private boolean flag=false;
    private String name;
     //Thread-0 Thread-1
    public synchronized void setValue(String name)
    {
        //if(flag)
         while(flag)
          try{this.wait();}catch(Exception e){}//
        
        this.name=name+"---"+count++;
        System.out.println(Thread.currentThread().getName()+"...---生产者生产----..."+this.name);
        flag=true;
        
        //this.notify();//
        this.notifyAll();
    }
    //Thread-2  Thread-3
    public synchronized void printValue()
    {
        //if(!flag)
         while(!flag)
           try{this.wait();}catch(Exception e){}//
      
        System.out.println(Thread.currentThread().getName()+"---消费者消费---"+name);
        flag=false;
      
      //this.notify();//
       this.notifyAll();
    }
}
class Producer implements Runnable
{
 private Resource r;
 Producer(Resource r)
 {
   this.r=r;
 
 }
 public void run()
 {
     int x=0;
     while (true)
      r.setValue("+商品+");
 }

}
class Consumer implements Runnable
{
 private Resource r;
 Consumer(Resource r)
 {
   this.r=r;
 
 }
 public void run()
 {
     int x=0;
     while (true)
       r.printValue();

     

 
 }

}
class ProCon
{
    public static void main(String[] args)
    {
      Resource r=new Resource();
      /*
      //这样写也是可以的,虽然是两个生产者/消费者 对象,但
      //都是执行的r对象中的方法.
      new Thread(new Producer(r)).start();
      new Thread(new Producer(r)).start();
      
      new Thread(new Consumer(r)).start();
      new Thread(new Consumer(r)).start();
      
      */
      Producer p=new Producer(r);
      new Thread(p).start();
      new Thread(p).start();
      Consumer c=new Consumer(r);
      new Thread(c).start();
      new Thread(c).start();
      
    }
}
/*
打印结果:可能出现两次生产,只消费一次.生产一次,消费两次...
分析:(其中一种可能:生产两次,消费一次)

0      1.cpu切换到0线程->执行生产->Thread-0 生产者生产 商品 1->再次执行->
         0线程等待①位置. 

0 1    2.cpu切换到1线程->1线程等待①位置. 

1      3.cpu切换到2线程->执行消费->Thread-2 消费者消费 商品 1->④notify 0->
1 2       ->再次执行->2线程等待③位置.

1 2 3  4.cpu切换到3线程->3线程等待③位置


2 3    5.cpu切换到0线程->从①开始执行->Thread-0 生产者生产 商品 2->②notify 1->
2 3 0    ->再次执行->0线程等待①位置


3 0    6.cpu切换到1线程->从①开始执行->Thread-1 生产者生产 商品 3->②notify 2
3 0 1    ->再次执行->1线程等待①位置

       
       
0 1     7.cpu切换到2线程->从③开始执行->Thread-2 消费者消费 商品 3->④notify 3
0 1 2     ->再次执行->2线程等待③位置

    
    ......
另一种可能:先生产即可. 

以上关键在5,6 0线程唤醒1线程,而1线程没有执行flag判断,继续向下执行.
->如果把if改成while(flag)/while(!flag)
->在第五步0唤醒1->1去等待,0也去等待->全部等待

那么使用notifyAll()全部唤醒
实际上 本方依然等待,而让对方执行.
*/

                 生产者消费者正常

生产者-消费者JDK5.0升级(Lock,Condition)

/*
JDK1.5版本中提供了多线程的升级解决方案
(显式的锁机制)
将同步Synchronized替换成现实的Lock操作
将Object中的wait,notify,notifyall,替换成了Condition对象.
该对象可以通过Lock锁进行获取(newCondition())
在该示例中,实现了本方唤醒对方的操作

*/

import java.util.concurrent.locks.*;
class Resource   
{
    private int count=1;
    private boolean flag=false;
    private String name;
    private Lock lock=new ReentrantLock();//锁对象,ReentrantLock类实现Lock
    private Condition condition_pro=lock.newCondition();    
    private Condition condition_cus=lock.newCondition();                                                           
     //Thread-0 Thread-1                                         
    public  void setValue(String name)throws InterruptedException 
                                                                     
   {     
       lock.lock();
      System.out.println(Thread.currentThread().getName()+"①---"+flag);
       try
       {                                      
          if(flag)
           condition_pro.await();//与此 Condition 相关的锁以原子方式释放

      System.out.println(Thread.currentThread().getName()+"②---"+flag);
        this.name=name+"---"+count++;
     System.out.println(Thread.currentThread().getName()+"...---生产者生产----..."+this.name);
        flag=true;
        //condition.signal();
        //condition.singalAll();
         condition_cus.signal();
    System.out.println(Thread.currentThread().getName()+"③---"+flag);
        }
        finally
        { 
         System.out.println("---unlock---");
         lock.unlock();//必须finally,有可能await抛出异常不释放锁
    
        }
    }
    //Thread-2  Thread-3
    public synchronized void printValue()throws InterruptedException
    {
        lock.lock();
   System.out.println(Thread.currentThread().getName()+"④---"+flag);
        try
        {
             if(!flag)
                condition_cus.await();
    System.out.println(Thread.currentThread().getName()+"⑤---"+flag);
             System.out.println(Thread.currentThread().getName()+"---消费者消费---"+name);
             flag=false;
             //condition.signal();全等待
             //condition.signalAll();
             condition_pro.signal();//唤醒condition_pro对象上的await(其中一个线程)
    System.out.println(Thread.currentThread().getName()+"⑥---"+flag);
        }

        finally
        { 
        System.out.println("---UNLOCK---");
          lock.unlock();
        
        }
    
     
    }
}
class Producer implements Runnable
{
 private Resource r;
 Producer(Resource r)
 {
   this.r=r;
 
 }
 public void run()
 {
     int x=0;
     while (true)
      try
      {
         r.setValue("+商品+");
      }
      catch (InterruptedException e)
      {
      }
        
 }

}
class Consumer implements Runnable
{
 private Resource r;
 Consumer(Resource r)
 {
   this.r=r;
 
 }
 public void run()
 {
     int x=0;
     while (true)
       try
       {
    
        r.printValue();
       }
       catch (InterruptedException e)
       {
       }
        

     

 
 }

}
class ProCon2
{
    public static void main(String[] args)
    {
      Resource r=new Resource();
      Producer p=new Producer(r);
      new Thread(p).start();
      new Thread(p).start();
      Consumer c=new Consumer(r);
      new Thread(c).start();
      new Thread(c).start();
      
    }
}
/*
   //Lock 替代了 synchronized 方法和语句的使用,
  //Condition 替代了 Object 监视器方法的使用
  //wait,notify在同步中需要标识所属的锁
  //因此通过Lock方法获取condition的实例  
  //也就是说Condition 实例实质上被绑定到一个锁上
*/

/*
以上仅仅替换了原先的代码(换汤不换药)
依然存在把本方线程唤醒的可能.
因此再使用一个condition对象
可以有多个实例绑到一个锁上,由此体现出新版本优点
同步中只能有一个对象绑定到一个锁.
*/

/*
虽然唤醒的是对方线程,但是依然需要while判断标记:
否则依然出现生产两次,而消费一次:
cpu切换到0->0生产->在执行->0等待
cpu切换到2->2消费->唤醒0->在执行->2等待
cpu切换到1->1生产
cpu切换到0->0生产
...
*/

interrupt方法:

/*
stop 方法已经过时.
如何停止线程?
只有一种,run方法结束.
开启多线程运行,运行代码通常是循环结构
只要控制住循环,就可以让run方法结束,也就是线程结束.

Thread类中的interrupt方法:(不是终止线程)

    当没有指定方式让冻结的线程恢复到运行状态时,
    这时需要对冻结(阻塞)进行清除.强制让线程恢复到就绪状态中来.
    这样就可以操作标记让线程结束.
 清除的根本:使wait方法抛出InterruptedException


*/
class StopThread implements Runnable
{
  private boolean flag=true;
   public synchronized void run()
   {
    while(flag)
       {
            try
            {
                wait();//当线程处于阻塞状态->不会读取到标记->线程就不会结束
            }          //两个线程均等待
            catch (InterruptedException e)
            {
               
            System.out.println(Thread.currentThread().getName()+"....Exception...");   
              flag=false;
            }
            
            
            System.out.println(Thread.currentThread().getName()+"....run...");
       }
   }
   public void changeFlag()
   {
     flag=false;
   
   }

}
class StopThreadDemo
{
    public static void main(String[] args)
    {
      StopThread s=new StopThread();
      Thread t1=new Thread(s);
      Thread t2=new Thread(s);
      t1.start();
      t2.start();
      int x=0;
      while(true)
      {
        if(x++==60)
          {
            //s.changeFlag();
            t1.interrupt();
            t2.interrupt();
            break;
          }
       System.out.println(Thread.currentThread().getName()+"...."+x);
      }
      System.out.println(Thread.currentThread().getName()+"....over");
    }
}

 interrupt

守护(后台)线程:

 

/*
守护线程:
 当线程被标记为守护线程(后台线程)
 开启,执行与一般线程均无区别,
 但当所有前台线程(未标记)都执行完->守护线程会自动结束

该方法必须在启动线程前调用。
*/
class StopThread implements Runnable
{
  private boolean flag=true;
   public synchronized void run()
   {
    while(flag)
       {
        
        System.out.println(Thread.currentThread().getName()+"....run...");
       }
   }
   public void changeFlag()
   {
     flag=false;
   
   }

}
class setDaemonDemo
{
    public static void main(String[] args)
    {
      StopThread s=new StopThread();
      Thread t1=new Thread(s);
      Thread t2=new Thread(s);
      t1.setDaemon(true);
      t2.setDaemon(true);//on为true 该线程被标记为守护线程
      t1.start();
      t2.start();
      int x=0;
      while(true)
      {
        if(x++==60)
          {
          
            break;
          }
       System.out.println(Thread.currentThread().getName()+"...."+x);
      }
      System.out.println(Thread.currentThread().getName()+"....over");
    }
}
/*
主线程一执行完,t1,t2自动结束.

*/

守护线程

为什么在主线程”over”后,守护线程又执行了一会?其实并非这样,之所以在over后又打印了守护线程的输出语句这是因为:可能先执行后被打印

join方法:

/*
join 方法:
当A线程执行到了B线程的.join方法时,A线程就会等待,
等B线程都执行完,A才会执行

join可以用来临时加入线程.
*/
class Demo implements Runnable
{
 public void run()
 {
  
  for(int i=0;i<70;++i)
  {
   System.out.println(Thread.currentThread().getName()+"....."+i);
  
  
  }
 
 }

}
class JoinDemo
{
    public static void main(String[] args) throws Exception
    {
      
      Demo d=new Demo();
      Thread t1=new Thread(d);
      Thread t2=new Thread(d);
      t1.start();
      t2.start(); 
      t1.join();//可以用于临时加入一个线程,让该线程执行完.
                //当主线程执行到此,把cpu执行权交给t1,主线程会等t1的run()结束后,主线程才能继续执行
                //而t2是否执行结束不影响主线程
      for(int i=0;i<80;++i)
      {
       System.out.println(Thread.currentThread().getName()+"....."+i);
      
      }
    }
}

yield方法:

class Demo implements Runnable
{
 public void run()
 {
  
  for(int i=0;i<70;++i)
  {
   System.out.println(Thread.currentThread().getName()+"....."+i);
   Thread.yield();//稍微减缓线程运行,0执行下,1执行下,0执行下...
  
  }
 
 }

}
class YieldDemo
{
    public static void main(String[] args) throws Exception
    {
      
      Demo d=new Demo();
      Thread t1=new Thread(d);
      Thread t2=new Thread(d);
      t1.start();
      t2.start(); 
      //t1.setPriority(Thread.MAX_PRIORITY);//使0线程具有更高优先级(1-10,默认为5)
      for(int i=0;i<80;++i)               //cpu执行该线程频率高点
      {
       System.out.println(Thread.currentThread().getName()+"....."+i);
      
      }
    }
}
posted @ 2013-03-20 21:40  伊秋  阅读(357)  评论(0编辑  收藏  举报