黑马程序员 多线程通信

------- android培训java培训、期待与您交流! ----------


在学习java的过程中,我们经常能碰到多个线程任务操作一个共享数据的情况,反应到生活中,比如多个窗口卖票,票的总数固定,有的窗口卖的多有的卖的少,但是每一张票在多个窗口只能被卖出去一次。像涉及到多线程操作共享数据的情况,我们一般把这些情况称为多线程通信。在多线程通信中有哪些要注意的呢,或者说怎么编译好一个多线程通信的程序呢。我觉得有以下几个要点

  1.  明确要操作的共享数据
  2.  明确共享数据操作的前提
  3.  明确需要同步的代码段

在多线程通信中少不了线程同步,因为多线程通信容易导致共享数据出错的原因就是在一个线程还没完全处理共享数据的时候就被挂起切换到另一个线程操作共享数据了。明确需要同步的代码段有个诀窍,那就是谁操作了共享数据,谁就需要被同步。关于线程同步线程同步有两种方法,一种是使用synchronized方法修饰需要被同步的代码,二是利用Lock类中的lock方法和unlock方法来确定需要同步的代码块。无论是哪个方法都需要确认同步所需要的监视器,而且要保证操作共享数据的线程同步是使用同一个监视器。来看一个小例子:

/* 需求:多线程售票
 * */
class Ticket implements Runnable{
   //使用实现Runnable方法可以创建新线程以及运行,该方法比继承Thread类要好
   private int ticketnum=200;
   //复写Runnable类中的run方法
   public void run()
   {
      while(true)
      {
         saleticket();
      }
   }
  
   //将涉及到共享数据的方法加入synchronized可以保证只有1个线程完全执行完该方法下一个线程才能进入执行
   private synchronized void saleticket()
   {
         if(ticketnum>0)
         {
            try
            {
                Thread.sleep(10);
                //等待10ms,由于该sleep值可能出现运行异常(runtime),故加入异常处理
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":"+ticketnum);
            //Thread.currentThread().getName()获取当前线程对象的名字,不能等于this.getName
            ticketnum--;
         }
     
   }
}
public classSaleTicket {
 
   public static void main(String[] args) {
      //建立实现Runnable接口的子类对象
      Tickettic=new Ticket();
     
      //将Runnable的子类对象传递给Thread并建立一个新线程的对象
      Threadsale_1=new Thread(tic,"sale_1");
      Threadsale_2=new Thread(tic,"sale_2");
      Threadsale_3=new Thread(tic,"sale_3");
      Threadsale_4=new Thread(tic,"sale_4");
     
      //调用Thread对象中的start方法会调用传递进来的类参数的run方法
      sale_1.start();
      sale_2.start();
      sale_3.start();
      sale_4.start();
   }
 
}
 

 

在这里为了保证操作的共享数据只有一份,只建立了一个Ticket的实例对象,而需要被操作的共享数据是这个对象内的ticketnum变量。再把这个实例类传值给Thread类建立线程。也就是说多个线程操作的都是同一个Ticket对象的ticketnum变量。这样就保证了共享数据的唯一性。确定了共享数据后就是确定需要同步的代码段,秉着谁操作贡献数据谁就需要被同步的原则,在这个例子中同步代码段则被单独封装成一个saleticket方法。由于这里四个线程是并发执行的,直接并没有逻辑关系,所以不存在共享数据操作的前提。

按照这三步就完成了一个简单的小程序,四个线程同时卖票,票在卖完之后线程立即停止。

多线程多种操作方式

在程序设计的时候,我们往往碰到多个线程操作一个共享数据,但是操作共享数据的方法却不一样。这个时候就是多线程多种操作方式。比如生活中的一个实例,一个工厂的仓库一边有生产好的产品放入仓库,一边又有将产品运出仓库。这样操作同样一个共享数据仓库产品的两个或多个线程都的操作方式又都不一样。在这个例子中涉及到三个新的方法wait,notify和notifyAll,这三个方法的作用是:

 

1. wait();  调用该方法的进程将进入冻结状态。当一个线程被冻结后只能通过其他的线程将其唤醒。

2. notify();    唤醒一个冻结的线程,解除其等待状态,一般是唤醒最早冻结的那个线程。

3. notifyAll();    唤醒全部被冻结的线程,解除等待状态。

 

在前面那个仓库实例中,运出产品的操作必须在仓库有产品的时候才能操作,而生产出来的产品必须在仓库空了有位置放了才能放入仓库。

明确了这个例子中的三个要素,一是要操作的共享数据是仓库的产品,二是有一个线程在仓库有产品的时候运出产品,一个线程在仓库没产品的时候运进产品。即明确了各个线程操作共享数据的前提。三是明确需要同步的代码段即操作了同步数据的代码段。那么我们可以写出程序如下:

  

packagecom.pritice_sawa;
/*
 * 需求:做一个输入输出的例子,实现线程间的通信
*/
 
class aInputExample implements Runnable
{
   privateOutInputExample s;
   aInputExample(OutInputExamples)
   {
      this.s=s;
   }
   publicvoid run()
   {
      while(true)
         s.input();
   }
}
 
class aOutputExample implements Runnable
{
   privateOutInputExample s;
   aOutputExample(OutInputExamples)
   {
      this.s=s;
   }
   publicvoid run()
   {
      while(true)
         s.output();
   }
}
 
class OutInputExample
{
   privateboolean flag=false;//共享数据,其值决定了线程的操作
   privatestatic int i=0;
   publicsynchronized void input()
   {
      if(flag)//输入,若共享数据为true,则让共享数据变为false
                //在实例中就是仓库中没有产品则运入。
         try{wait();}catch(Exceptione){}
      System.out.println(Thread.currentThread().getName()+i++);
      flag=!flag;
      this.notify();
   }
   publicsynchronized void output()
   {
      if(!flag)//输出,若共享数据为false,则让共享数据变为true
                //在实例中就是仓库中有产品则运出。
         try{wait();}catch(Exceptione){}
      System.out.println(Thread.currentThread().getName()+i++);
      flag=!flag;
      this.notify();
   }
}
 
public class InputOutputThread {
   publicstatic void main(String[] args)
   {
      OutInputExamples=new OutInputExample();
      aInputExamplein=new aInputExample(s);
      aOutputExampleout=new aOutputExample(s);
     
      Threadt01=new Thread(in,"Input:");
      Threadt02=new Thread(out,"Output:");
     
      t01.start();
      t02.start();
     
   }
 
}

 

在这个例子中可以看到共享数据和操作共享数据的方法都被封装在一个类中,在建立线程的时候仅建立这个类的一个对象,对于不同的操作方式则在线程中调用该类中的不同的操作方法。

Lock与Condition

Lock对象是同步方法的一个可视化改善。使用lock对象可以清楚地看出需要同步的代码块以及其使用的监视器。而Condition对象是绑定在lock上的对象,其特有的方法singal,singalAll可以实现类似于notify或notifyAll;

下面这个例子详细的讲述了Lock 和Condition对象的使用。

 

package com.pritice_sawa;
 
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
importjava.util.concurrent.locks.ReentrantLock;
 
/*
 * 需求:做一个输入输出的例子,实现多线程间的通信,其中使用lock锁,以及2线程输入2线程输出
 *
 * */
 
class aInputExample2 implements Runnable
{
   //建立一个Input类,并建立一个给定类的对象。构造函数中指向给定类的对象。
   //这样的原因是当要执行给定类的方法的时候建立多个线程都能指向同一个类的对象。
   privateOutInputExample2 s;
   aInputExample2(OutInputExample2s)
   {
      this.s=s;
   }
   publicvoid run()
   {
      while(true)
         //run方法中调用给定类的方法
         s.input();
   }
}
 
class aOutputExample2 implements Runnable
{
   //建立一个Output类,并建立一个给定类的对象。构造函数中指向给定类的对象。
   //这样的原因是当要执行给定类的方法的时候建立多个线程都能指向同一个类的对象。
   privateOutInputExample2 s;
   aOutputExample2(OutInputExample2s)
   {
      this.s=s;
   }
   publicvoid run()
   {
      while(true)
         //run方法中调用给定类的方法
         s.output();
   }
}
 
class OutInputExample2
{
   //建立给定类,该类中包含有线程通信所操作的全部共享数据,一般该类只建立1个对象并指向多条线程
   privateboolean flag=false;
   private  int i=0;//共享数据
   finalLock lock = new ReentrantLock();
   //建立锁!
   finalCondition condition  =lock.newCondition();
 
   public  void input()
   {
      lock.lock();//锁和解锁,lock&unlock组成同步,其中间代码需要同步运行
         try
         { 
            while(flag)
                condition.await();//等待会出现异常故这里需要异常处理
            //这里实质上将线程和condition对象绑定了,一个线程调用了condition对象await方法时会冻结
            //只有另一个线程再次调用同一个condition对象的对应方法才能唤醒,其方法有signalAll()&signal()
            //signalAll():唤醒绑定在该condition对象上所有冻结的线程
            //signal():唤醒绑定在该condition对象上的一个线程(一般为等待时间最长的线程)
            System.out.println(Thread.currentThread().getName()+i++);
            flag=!flag;
            condition.signalAll();
         }
         catch(Exceptione)
         {
           //...
         }
         finally
         {
            lock.unlock();
         }
     
   }
   public  void output()
   {
      lock.lock();
         try
         { 
            while(!flag)
                condition.await();
            System.out.println(Thread.currentThread().getName()+i++);
            flag=!flag;
            condition.signalAll();
         }
         catch(Exceptione)
         {
           //....
         }
         finally
         {
            lock.unlock();
         }
   }
}
 
public class InputOutputThread2 {
   publicstatic void main(String[] args)
   {
 
      OutInputExample2s=new OutInputExample2();
      //建立给定类的对象,该类中包含了共享数据以及线程的通信操作方法
      aInputExample2in=new aInputExample2(s);
      //建立input对象,该类用于放入数据,其run方法指向OutInputExample2中的input方法
      aOutputExample2out=new aOutputExample2(s);
      //建立output对象,该类用于拿出数据,其run方法指向OutInputExample2中的output方法
      //这样建立线程操作的都是OutInputExample2对象中的共享数据,由于该类对象只建立了一个,在这里数据唯一
     
      Threadt01=new Thread(in,"Input1:");
      Threadt02=new Thread(in,"Input2:");
      Threadt03=new Thread(out,"Output1:");
      Threadt04=new Thread(out,"Output2:");
     
      t01.start();
      t02.start();
      t03.start();
      t04.start();
     
   }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
黑马程序员多线程练习题主要包括两个问题。第一个问题是如何控制四个线程在打印log之前能够同时开始等待1秒钟。一种解决思路是在线程的run方法中调用parseLog方法,并使用Thread.sleep方法让线程等待1秒钟。另一种解决思路是使用线程池,将线程数量固定为4个,并将每个调用parseLog方法的语句封装为一个Runnable对象,然后提交到线程池中。这样可以实现一秒钟打印4行日志,4秒钟打印16条日志的需求。 第二个问题是如何修改代码,使得几个线程调用TestDo.doSome(key, value)方法时,如果传递进去的key相等(equals比较为true),则这几个线程应互斥排队输出结果。一种解决方法是使用synchronized关键字来实现线程的互斥排队输出。通过给TestDo.doSome方法添加synchronized关键字,可以确保同一时间只有一个线程能够执行该方法,从而实现线程的互斥输出。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [黑马程序员——多线程10:多线程相关练习](https://blog.csdn.net/axr1985lazy/article/details/48186039)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值