1.红绿灯:

(1)交通等管理系统的项目需求

模拟实现十字路口的交通灯管理逻辑系统,具体需求如下:

异步随机生成按照各个路线行驶的车辆。

例如:

     由南向而来去往北向的车辆---直行车辆

---右转车辆

---左转车辆

     。。。

     信号灯忽略黄灯,只考虑红灯和绿灯

应考虑左转车辆控制信号灯,右转车辆不受信号灯控制。

具体信号灯控制逻辑与现实生活中普通交通灯控制逻辑相同,不考虑特殊情况下的控制逻辑。

注:南北向车辆与东西向车辆交替放行,同方向等待车辆应先放行直行车辆而后放行左转车辆。

每辆车通过路口时间为1秒。

随机生成车辆时间间隔以及红绿灯交换时间间隔自定,可以设置。

不要求实现GUI,只考虑系统逻辑实现,可通过Log方式展现程序运行结果。


如图,先考虑南向的灯,先直行的车走,再左转的车走

Java红绿灯问题 java多线程红绿灯编程题_Java红绿灯问题

总共有12条路线,为了统一编程模型,可以假设每条路线都有一个红绿灯对其进行控制,右转弯的4条路线的控制灯可以假设成为常绿状态,另外,其他的8条线路是两两成对的,可以归为4组,所以,程序只需考虑图中标注了数字号的4条路线的控制灯的切换顺序,这4条路线相反方向的路线的控制灯跟随这4条路线切换,不必额外考虑。

 

对象:红绿灯,红绿灯控制系统,路线。

路对象存储车辆的集合,提供增加和减少车辆的方法。

 

(2)面向对象的分析与设计

每条路线上都会出现多辆车,路线上要随机增加新的车,在灯绿期间还要每秒钟减少一辆车。

设计一个Road类来表示路线,每个Road对象代表一条路线,总共有12条路线,及系统中总共要产生12个Road实例对象。

每条路线上随机增加新的车辆,增加到一个集合中保存。

每条路线每隔一秒都会检查控制本路线的灯是否为绿,是则将本路线保存车的集合中的第一辆车移除,即表示车穿过了路口。

每条路线每隔一秒种都会检查控制本路线的灯是否为绿,一个灯由绿变红时,应该将下一个方向的灯变绿。

设计一个Lamp类来表示一个交通灯,每个交通灯都维护一个状态:亮(绿)或不亮(红),每个交通灯要有变亮和变黑的方法,并且能返回自己的亮黑状态。

总共有十二条路线,所以,系统中总共要产生12个交通灯。右转弯的路线本来不受灯的控制,但是为了让程序采用统一的处理方式,故假设出有四个右拐弯的灯,只是这些灯为常量状态,即永不变黑。

除了右拐弯方向的其他8条路线的灯,他们是两两成对的,可以归结为4组,所以,在编程处理时,只要从这4组中各取出一个灯,对这4个灯依次轮询变亮,与这4个灯对应的灯则随之一同变化,因此Lamp类中要有一个变量来记着自己相反方向的灯,在一个Lamp对象的变亮和变黑方法中,将对应方向的灯也变量和变黑,每个灯变黑时,都伴随着下一个灯的变亮。

 

(3)一共有四个类:

1、Road类:

1、定义一个集合,存储所有的车子。

2、定义一个线程池,每隔1-10秒往集合中添加车子。

3、定义一个定时器,首先判断灯是否为绿灯,如果灯为绿,每隔一秒将路

   上的第一辆车从集合中移除,模拟车子经过十字路口。

代码例子:

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Road
{

定义一个集合,用于存储车辆。

    private List<String> vechicles = new ArrayList<String>();

路线的名字。

   private String name =null;

   public Road(String name)

   {

      this.name = name;

模拟车辆不断随机上路的过程

     ExecutorService pool = Executors.newSingleThreadExecutor();

     pool.execute(new Runnable()

     {

        public void run()

        {

            for(int i=1;i<1000;i++)

           {

              try 

              { 

秒到10秒随机的产生车辆。

                 Thread.sleep((new Random().nextInt(10) + 1) * 1000);

              } 

             catch (InterruptedException e)

             {

                 e.printStackTrace();

              }

将某条路线上的某辆车添加进集合。

                 vechicles.add(Road.this.name + "_" + i);

            }

         }

    });

每隔一秒检查对应的灯是否为绿,是则放行一辆车,定义一个计时器。

   ScheduledExecutorService timer =  Executors.newScheduledThreadPool(1);

   timer.scheduleAtFixedRate(new Runnable()

   {

        public void run()

        {

检查集合是否为空,也就是检查路上有没有车。

            if(vechicles.size()>0)

            {            

                 boolean lighted = Lamp.valueOf(Road.this.name).isLighted();

                if(lighted)

                {               

                   System.out.println(vechicles.remove(0) + " is traversing !");

                 }

              }

           }

        },1,1,TimeUnit.SECONDS);

     }

}

2、Lamp类:

定义一个枚举类,枚举类中添加12个元素,代表十二个方向的车,每个元素以对面的灯,下一个灯,及灯现在的状态为参数。

定义灯的点亮方法,将自己和对面的灯点亮。

定义灯的熄灭方法(变红),将自己和对面的灯都熄灭,并让自己的下一盏灯点亮,这个方法同时返回一个Lamp对象,即为当前被点亮的灯对象,方便LampController调用。

代码例子:

public enum Lamp

{

      //每个枚举元素各表示一个方向的灯对象。

      S2N("N2S","S2W",false),S2W("N2E","E2W",false),E2W("W2E","E2S",false),E2S("W2N","S2N",false),

     //下面元素表示与上面的元素的相反方向的灯,它们的“相反方向灯”和“下一个灯”应忽略不计。

      N2S(null,null,false),N2E(null,null,false),W2E(null,null,false),W2N(null,null,false),

      //由南向东和由西向北等右拐弯的灯设为常绿灯。

      S2E(null,null,true),E2N(null,null,true),N2W(null,null,true),W2S(null,null,true);

      private Lamp(String opposite,String next,boolean lighted)

      {

         this.opposite = opposite;

         this.next = next;

         this.lighted = lighted;

      }

当前灯是否为绿。

         private boolean lighted;

与当前灯同时为绿的对应方向。

         private String opposite;

当前灯变红时下一个变绿的灯。

         private String next;

         public boolean isLighted()

         {

              return lighted;

          }

 /**

某个灯变绿时,它对应方向的灯也要变绿

 */

     public void light()

    {

       this.lighted = true;

       if(opposite != null)

       {

          Lamp.valueOf(opposite).light();

        }

,下面总共应该有6个方向能看到汽车穿过!");

      }

/**

某个灯变红时,对应方向的灯也要变红,并且下一个方向的灯要变绿

下一个要变绿的灯

 */

    public Lamp blackOut()

   {

       this.lighted = false;

      if(opposite != null)

      {

         Lamp.valueOf(opposite).blackOut();

    }

     Lamp nextLamp= null;

     if(next != null)

     {

         nextLamp = Lamp.valueOf(next);

绿灯从" + name() + "-------->切换为" + next);

        nextLamp.light();

     }

     return nextLamp;

   }

}

 

3、LampController类:

     首先点亮枚举类中的第一个方向的灯,该灯点亮,则其相反方向的灯同时点亮。

定义一个定时器,每隔10秒钟调用灯的熄灭方法,则下一个方向及其相反方向的灯被点亮。

代码例子:

import java.util.concurrent.Executors;

import java.util.concurrent.ScheduledExecutorService;

import java.util.concurrent.TimeUnit;

 

public class LampController

 {

     private Lamp currentLamp;

     public LampController()

    {

刚开始让由南向北的灯变绿;

      currentLamp = Lamp.S2N;

      currentLamp.light();

每隔10秒将当前绿灯变为红灯,并让下一个方向的灯变绿。

定义了线程池,里面有一个线程。

   ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);

    timer.scheduleAtFixedRate(

               new Runnable()

              {

                  public  void run()

                 {

来啊");

把当前这个灯变黑,并把下一个方向的灯变绿,

*/           

                       currentLamp = currentLamp.blackOut();

                 }

            },10,10,TimeUnit.SECONDS); 

      }

 

4、MainClass类:

代码例子:

public class MainClass 

{

    public static void main(String[] args)

    {

产生12个方向的路线对象。

      String [] directions = new String[]

      {

          "S2N","S2W","E2W","E2S","N2S","N2E",

         "W2E","W2N","S2E","E2N","N2W","W2S"

       };

       for(int i=0;i<directions.length;i++)

       {

          new Road(directions[i]);

       }

产生整个交通灯控制系统

        new LampController();

      }

2.银行业务调度系统

(1)需求:

模拟实现银行业务调度系统逻辑,具体需求如下:

1、银行内有6个业务窗口,1 - 4号窗口为普通窗口,5号窗口为快速窗口,6号窗口为VIP窗口。

2、有三种对应类型的客户:VIP客户,普通客户,快速客户(办理如交水电费、电话费之类业务的客户)。

3、异步随机生成各种类型的客户,生成各类型用户的概率比例为:

客户 :普通客户 :快速客户  =  1 :6 :3。

4、客户办理业务所需时间有最大值和最小值,在该范围内随机设定每个VIPSleep的方式模拟)。

5、各类型客户在其对应窗口按顺序依次办理业务。 

6、当VIP(6号)窗口和快速业务(5号)窗口没有客户等待办理业务的时候,这两个窗口可以处理普通客户的业务,而一旦有对应的客户等待办理业务的时候,则优先处理对应客户的业务。

7、随机生成客户时间间隔以及业务办理时间最大值和最小值自定,可以设置。

8、不要求实现GUI,只考虑系统逻辑实现,可通过Log(日志)方式展现程序运行结果。 

(2)类图:

Java红绿灯问题 java多线程红绿灯编程题_java_02

(3)面向对象的分析与设计:

 

有三种类型的客户:

1、VIP客户,普通客户,快速客户。

2、异步随机生成各种类型的客户,各种类型客户在其对应窗口按顺序依次办理业务。

3、号码管理器对象,让这个对象不断地产生号码,就等于随机生成了客户。

4、由于有三类客户,每类客户的号码编排都是完全独立的,所以,想到本系统一共要产生三个号码管理器对象,各自管理一类用户的排队号码。这三个号码管理器对象统一由一个号码机器进行管理,这个号码机器在整个系统中只能有一个,所以,它要被设计成单例。

5、各类型客户在其对应的窗口依次办理业务,准确地说,应该是窗口依次叫号。

各个窗口怎么知道应该叫哪一个号呢?他一定是问相应的号码管理器,即服务窗口每次找号码管理器获取当前要被服务的号码。

(4)需要六个类:

1、NumberManager类

用于实现取号功能,将号码加入队列中如果队列中号码个数大于零,则取出队列中的第一个。因为是多线程,加synchronized。

代码例子:

public class NumberManager //号码发生器

{

上一次返回的数字。

     private int lastNumber = 1;

正在排队的号码,定义一个集合用来存这些号码。

    private List<Integer> queueNumbers = new ArrayList<Integer>();

生成新号码。

    public synchronized Integer generateNewNumber()

    {

返回下一个号码,并添加到集合中,因为需要排队。

       queueNumbers.add(++lastNumber);

       return lastNumber;

     }

窗口取的方法。也就是该为第几号客户服务了,因为生成号码的方法

       和取号码的方法都访问到了同一个资源,是不同的线程所以就会出现

*/

     public synchronized Integer fetchNumber()

    {                                           

如果集合中还有排队的号码,也就是还有客户需要半业务,那么就继续取号。   

        if(queueNumbers.size()>0)

        {

           return queueNumbers.remove(0);

         } 

如果没有就返回null,也就是没有客户办理业务了。

        else

        {

            return null;

         }

     }

}

 

2、NumberMachine类

定义得到号码管理器对象的功能,因为号码生成器只有一个,需要使用单例设计模式。

代码例子:

public class NumberMachine //号码管理器。管理3个号码

{

将号码管理器设置成单例,因为因为这个对象在系统中只有一个。

   private NumberMachine(){}

   private static NumberMachine instance = new NumberMachine();

   public static NumberMachine getInstance()

  {

     return instance;

   }

要返回3个号码管理器。

普通客户号码管理器。

     private NumberManager commonManager = new NumberManager();

快速客户号码管理器。

     private NumberManager expressManager = new NumberManager();

客户号码管理器。

     private NumberManager vipManager = new NumberManager();

     public NumberManager getCommonManager() 

     {

        return commonManager;

     }

     public NumberManager getExpressManager()

     {

      return expressManager;

   }

    public NumberManager getVipManager() 

    {

       return vipManager;

     }

}

 

 

3、ServiceWindow类

定义开始叫号的方法,定义一个进程池,根据不同的客户执行不同的服务代码

普通客户服务代码,获取对象,如果有对象,为其服务,结束后,再次调用方法,如果没有,先休息一秒钟,再获取对象快速客户服务代码,获取对象,如果有对象,为其服务,结束后,再次调用方法,如果没有对象,获取普通客户对象,为普通客户服务,服务时间固定为一秒VIP客户服务代码,获取对象,如果有对象,为其服务,结束后,再次调用方法,如果没有对象,获取普通客户对象,为普通客户服务将程序中用到的常量在单独的类中定义,单独定义一个枚举类,存放客户类型,并复写toString()方法。

代码例子:

 public class ServiceWindow

{

    private static Logger logger = Logger.getLogger("cn.itcast.bankqueue");

获取普通窗口。

    private CustomerType type = CustomerType.COMMON;

不知道是第几个普通窗口,所以要定义一个顺序。

    private int number = 1;

 

   public CustomerType getType() 

   {

      return type;

    }

 

   public void setType(CustomerType type) 

   {

      this.type = type;

   }

   public void setNumber(int number)

   {

     this.number = number;

    }

有一个叫号的方法,需要用到线程,因为是自动的叫号。

   public void start()

   {

这里用到了线程池。

     Executors.newSingleThreadExecutor().execute(

建立一个线程。

        new Runnable()

        {

            public void run()

            {

下面这种写法的运行效率低,最好是把while放在case下面

不停的取号码。

               {

                   switch(type)

                  {

那么取哪一种号码的?所以就要定义不同的窗口。

                      case COMMON:

                     commonService();

                     break;

                     case EXPRESS:

                     expressService();

                     break;

                     case VIP:

                     vipService();

                     break;

                  }

               }

           }

       }

   );

}

private void commonService()

{

第" + number + "号" + type + "窗口";

开始获取普通任务!");

   Integer  serviceNumber = NumberMachine.getInstance().getCommonManager().fetchNumber();

   if(serviceNumber != null )

   {

开始为第" + serviceNumber + "号普通客户服务");

      int maxRandom = Constants.MAX_SERVICE_TIME - Constants.MIN_SERVICE_TIME;

      int serviceTime = new Random().nextInt(maxRandom)+1 + Constants.MIN_SERVICE_TIME;

      try

      {

             Thread.sleep(serviceTime);

       } 

      catch (InterruptedException e) 

      {

           e.printStackTrace();

       }

完成为第" + serviceNumber + "号普通客户服务,总共耗

" + serviceTime/1000 + "秒");

   }

  else

  {

没有取到普通任务,正在空闲一

     try

    {

       Thread.sleep(1000);

    } 

    catch (InterruptedException e)

    {

        e.printStackTrace();

     }

   }

}

  private void expressService()

  {

    Integer serviceNumber = NumberMachine.getInstance().getExpressManager().fetchNumber();

第" + number + "号" + type + "窗口";

开始获取快速任务!");

    if(serviceNumber !=null)

    {

开始为第" + serviceNumber + "号快速客户服务");

      int serviceTime = Constants.MIN_SERVICE_TIME;

      try

      {

        Thread.sleep(serviceTime);

      } 

     catch (InterruptedException e) 

     {

       e.printStackTrace();

     }

完成为第" + serviceNumber + "号快速客户服务,总共耗时" + serviceTime/1000 + "秒");

}

  else

  {

没有取到快速任务!");

      commonService();

   }

}

private void vipService()

{

     Integer serviceNumber = NumberMachine.getInstance().getVipManager().fetchNumber();

第" + number + "号" + type + "窗口";

开始获取VIP任务!");

     if(serviceNumber !=null)

     {

开始为第" + serviceNumber + "号VIP客户服务");

       int maxRandom = Constants.MAX_SERVICE_TIME - Constants.MIN_SERVICE_TIME;

       int serviceTime = new Random().nextInt(maxRandom)+1 + Constants.MIN_SERVICE_TIME;

        try 

        {

           Thread.sleep(serviceTime);

        } 

        catch (InterruptedException e) 

        {

            e.printStackTrace();

         }

完成为第" + serviceNumber + "号VIP客户服务,总共耗时" + serviceTime/1000 + "秒");

}

   else

   {

没有取到VIP任务!");

     commonService();

    }

  }

}

 

4、CustomerType枚举类
系统中有三种类型的客户,所以用定义一个枚举类,其中定义三个成员分别表示三种类型的客户。

代码例子:

//服务窗口只有3种,为了确定再没有别的窗口,就用到了枚举。

public enum CustomerType

{

    COMMON,EXPRESS,VIP;

     public String toString()

    {

         String name = null;

         switch(this){

         case COMMON:

普通";

         break;

         case EXPRESS:

快速";

          break;

         case VIP:

         name = name();

         break;

     }

     return name;

}

5、Constants类:

定义三个常量。
1、MAX_SERVICE_TIME

2、MIN_SERVICE_TIME

3、COMMON_CUSTOMER_INTERVAL_TIME

代码例子:

public class Constants 

{

秒。

秒。

     public static int COMMON_CUSTOMER_INTERVAL_TIME = 1; 

}

6、MainClass类:

按照题目要求,生成4个普通窗口,1个快速窗口,1个VIP窗口,调用窗口的start()方法,开始获取相应客户,获取相应客户后为之服务。建立线程池,按照客户比例设置相应的定时器间隔来生成客户。

 

代码例子:

public class MainClass

{

      private static Logger logger = Logger.getLogger("cn.itcast.bankqueue");

     public static void main(String[] args) 

     {

产生4个普通窗口

        for(int i=1;i<5;i++)

        {

           ServiceWindow window =  new ServiceWindow();

          window.setNumber(i);

         window.start();

         }

产生1个快速窗口

         ServiceWindow expressWindow =  new ServiceWindow();

        expressWindow.setType(CustomerType.EXPRESS);

        expressWindow.start();

产生1个VIP窗口

        ServiceWindow vipWindow =  new ServiceWindow();

        vipWindow.setType(CustomerType.VIP);

        vipWindow.start();

普通客户拿号

        Executors.newScheduledThreadPool(1).scheduleAtFixedRate(

new Runnable()

            {

               public void run()

              {

                 Integer serviceNumber = NumberMachine.getInstance().

                              getCommonManager().generateNewNumber()System.out.println("第" + serviceNumber + "号普通客

在等待服务!");                                   

              }

           },

           0,

          Constants.COMMON_CUSTOMER_INTERVAL_TIME, TimeUnit.SECONDS);

快速客户拿号

         Executors.newScheduledThreadPool(1).scheduleAtFixedRate(

new Runnable()

           {

                public void run()

               {

                Integer serviceNumber = NumberMachine.getInstance() .getExpressManager().generateNewNumber();

第" + serviceNumber + "号快速客户正在等待服务!");

               }

           },

          0,

          Constants.COMMON_CUSTOMER_INTERVAL_TIME * 2, TimeUnit.SECONDS);

客户拿号

          Executors.newScheduledThreadPool(1).scheduleAtFixedRate(

              new Runnable()

             {

                 public void run()

                {

                   Integer serviceNumber =  NumberMachine.getInstance() .getVipManager().generateNewNumber();

第" + serviceNumber + "号VIP客户正在等待服务!");

                }

            },

           0,

          Constants.COMMON_CUSTOMER_INTERVAL_TIME * 6, TimeUnit.SECONDS);

       }

}