1.1 案例介绍

本案例将演示在十字路口车辆运行的简单状况,用Java多线程技术和IBM提供的并发组件Amino实现在有交通信号灯控制情况下多车辆的运行的简单控制。

1.1.1 目的和意义

模拟交通信号控制下路口的交通情况,也是非常有实际意义的。在现实生活中,一个路口的交通信号灯可能要化很长的时间来调试。如果将这个过程用程序来模拟,那将可以节省时间,同时也可以考虑更多的复杂情况。

但是模拟交通信号控制下路口的交通情况,本身是一件较复杂的事情,因为要涉及到众多的线程,线程间涉及数据共享和同步。程序的调试也比较困难。

Java语言提供了良好的线程技术,同时Amino组件提供了并发安全的数据结构。本案例可以验证Amino组件在多线程情况下数据的正确性已以及程序的稳定性。

1.1.2 主要界面

本案例将演示在十字路口车辆运行的简单状况:先以1张背景图作为街道及十字路口的的模拟地形图。在本图上将显示双向两车道的带人行横道的十字街道。启动线程控制南北及东西四个方向的交通信号灯和信号的持续时间。在信号灯的指挥下,实现车辆的有限控制,如红灯停,绿灯行(黄灯按绿处理)等功能。程序的主要界面如图1。

1.1.3 主要功能

在本图上将显示双向两车道的带人行横道的十字街道。启动两个线程控制南北及东西四个方向的交通信号灯和信号的持续时间。

在有交通信号灯控制的场景中,启动一个线程再在各个路口随机添加车辆,每个车辆本身就是一个线程,它的运行要受到交通信号灯的简单控制,即红灯停,绿灯和黄灯通过。在十字路口中心的路口交汇区,车辆有可能撞车,本案例按照黄灯通过的方式作简单处理。

一辆车走完过了街道,即该车的线程结束了,该车也就在屏幕中消失了。

对于十字路口的街道,设置了一个车辆的运行总数。当场中的车辆没有达到运行总数,产生车辆的线程将继续向场中添加车辆。当场中的车辆达到运行总数,产生车辆的线程的将停止添加车辆。当有车辆消失后,场中的车辆少于运行总数,这时,产生车辆的线程的将继续添加车辆。也就是说,场中的车辆总数是一个动态的数目。同时,可以看到车辆运行是川流不息的。

     另外,在程序的菜单中,设置了整个系统的暂停和恢复。

1.1.4 主要操作流程.

启动程序后程序自动进入运行状态,默认总车辆数为20,但也可以进行一下的一些调整:

1) 流量控制,例如输入60,则系统将为信号灯的转换时间为15秒。

2)整个系统的暂停和恢复

1.2 安装运行.

本系统的运行需要Amnio组件和JavaSE6 的支持。

将附件的Eclispe工程导入Eclispe即可运行。

由于系统采用了Amino组件,需要下载Amino组件的amino-cbbs-0.3.1.jar文档,并添加到系统的库文件路径中去。

1.3 程序分析

分析程序的执行流程和主要功能的实现,并对主要的类进行说明。

在本程序中涉及到的类如下:

Trafficlight:主线程,启动其他线程;

Roadpanel:该类继承Jpanel,是需要显示的组件的容器,如信号灯、时间、车辆集合。

ImageFrame:主窗口,在上面添加roadpanel的实例,设置菜单等。

Controlew:控制东西方向信号灯的线程

Controlns:控制南北方向信号灯的线程

Car: 车辆线程

EnterRoad:随机向路口添加车辆的线程

Freshpanel:刷新显示Roadpanel的线程

ContralCarList:当线程正常结束,将Car的实例移出车辆集合。

Four:存放类变量的类,用来存放东西、南北方向信号灯对车辆速度影响的权重,其值为1或0,是一种全局公共变量的设置方式。

1.3.1 程序执行流程


1.3.2 多线程并发分析

在程序执行的过程中,会产生多个线程:

1) 东西信号灯控制线程,主要控制东、西两个方向的信号及信号持续时间,

2) 南北信号灯控制线程,主要控制南、北两个方向的信号及信号持续时间,

3) 添加车辆添加的线程,该线程每间隔一个随机时间后就会向场中添加一辆车子(每一辆车辆就是一个线程),

    该线程可能有很多。在默认情况下,场类将产生20辆车(20个线程)。

4) 车辆线程,车辆本身是一个运动的,需要一个线程模拟其运动规律。

5) 刷新线程,为了保证其显示效果,把车辆的重新绘制统一安排在一个线程中。

6) 车辆总数控制线程:该线程为独立线程,用来监控车线程是否是活的。当车的线程正常结束,将它的实例移出车辆集合。

在Roadpanel的实例中,我们设置了基于Amino组件的LockFreeList实体carlist,如下:

LockFreeList<Car> carlist=new LockFreeList<Car>();

LockFreeList实际上是一个线程安全的Amino并发组件,用来存放整个街道所有车辆。在不同的线程中,可以安全地对去做移出、添加等操作。

1.3.3 类说明

本程序主要包含的类分为一个包:sample.traff9包里面包含了程序的所有类:

                          表1-1 类说明

类名字

说明

Sample.traff9

Trafficlight

主线程,定义一些实例,启动其他线程;

Roadpanel

该类继承Jpanel,是需要显示的组件的容器,如信号灯、时间、车辆集合。

ImageFrame

主窗口,在上面添加roadpanel的实例,设置菜单等

Controlew

控制东西方向信号灯的线程

Controlns

控制南北方向信号灯的线程

Car:

车辆线程。

EnterRoad

随机向路口添加车辆的线程。

Freshpanel

刷新显示Roadpanel的线程。

ContralCarList

当线程正常结束,将Car的实例移出车辆集合。

Four

存放类变量的类,用来存放东西、南北方向信号灯对车辆速度影响的权重,其值为1或0,是一种全局公共变量的设置方式。

1.3.4 主要功能实现分析

下面对本程序中的主要功能作一定的分析。本程序中,主要有四个难点:东西信号灯、南北信号灯的转换及显示、车辆的随机添加和移出、车辆对红绿灯信号的响应以及整体动画显示的质量控制。

1) 东西信号灯、南北信号灯的转换及显示

东西信号灯控制类为Controlew,主要的代码如下:

class Controlew implements Runnable{
private int i=0,p,n,e,tp=0;
private String s="",w="",t="";
private Roadpanel rpanel;
Date date;
boolean v=true;
public Controlew(Roadpanel rp){
rpanel=rp; 
}
public void run()
{
out:while(true)
{
synchronized(this)
{ 
if(!v)  {
          try{
this.wait();
}catch(Exception e)
{e.printStackTrace();}
       }
rpanel.n1.setForeground(Color.green); Four.n1=0; 
rpanel.n2.setForeground(Color.red);  Four.e1=1;
rpanel.n3.setForeground(Color.red);  Four.w1=1;
rpanel.n.setForeground(Color.red);  Four.s1=0;
rpanel.e1.setForeground(Color.green);
rpanel.e2.setForeground(Color.green);
rpanel.e3.setForeground(Color.green);
rpanel.e.setForeground(Color.green);
rpanel.s1.setForeground(Color.red);
rpanel.s2.setForeground(Color.red);
rpanel.s3.setForeground(Color.green);
rpanel.s.setForeground(Color.red);
rpanel.w1.setForeground(Color.green);
rpanel.w2.setForeground(Color.green);
rpanel.w3.setForeground(Color.green);
rpanel.w.setForeground(Color.green);
tp=Integer.parseInt(rpanel.time.getText());
if(tp!=0)
{n=tp+1;}
else
{n=11;}
t.indexOf(n);
rpanel.n.setText(t);
rpanel.s.setText(t);
rpanel.e.setText(t);
rpanel.w.setText(t);
while(true)
{ 
date=new Date();
rpanel.d.setText(date.toString());
try{
Thread.currentThread().sleep(1000);
}catch(Exception e){} 
if(n==4)
{
rpanel.e2.setForeground(Color.yellow);
rpanel.e.setForeground(Color.yellow);
rpanel.e3.setForeground(Color.yellow);
rpanel.w2.setForeground(Color.yellow);
rpanel.w.setForeground(Color.yellow);
rpanel.w1.setForeground(Color.yellow); 
}
else
if(n==1)
break;
n--;
if(n<10)
s="0"+s.valueOf(n);
else
s=s.valueOf(n);
rpanel.n.setText(s);
rpanel.s.setText(s);
rpanel.e.setText(s);
rpanel.w.setText(s); 
}
v=false;
this.notify();
} 
} 
}
}

从上面的代码中我们可以看出,本线程首先设置东西方向的信号灯为“绿色”,南北向的信号灯为“红色”,如:

   

rpanel.e1.setForeground(Color.green);
 rpanel.e2.setForeground(Color.green);
 rpanel.e3.setForeground(Color.green);
 rpanel.e.setForeground(Color.green);
rpanel.w1.setForeground(Color.green);
 rpanel.w2.setForeground(Color.green);
 rpanel.w3.setForeground(Color.green);
 rpanel.w.setForeground(Color.green);

    也就是东西方向的车辆可以通行,而南北方向为“红色”信号灯。这时设置了车辆的速度增量权重:

Four.e1=1; Four.w1=1; Four.n1=0; Four.s1=0;

这四个值可以影响车辆的移动速度。

在Java中,每个对象都有个对象锁标志(Object lock flag)与之想关联,当一个线程A调用对象的一段synchronized代码时,它首先要获取与这个对象关联的对象锁标志,然后执行相应的代码,执行结束后,把这个对象锁标志返回给对象。

本类中,在synchronized代码被执行期间,线程可以调用对象的wait()方法,释放对象锁标志,进入等待状态,并且可以调用notify()方法通知正在等待的其他线程。这个“其他线程”就是南北信号灯控制线程Controlns。南北信号灯控制线程的代码如下:

class Controlns implements Runnable
{
private int i=0,p,n,e,tp;
private String s="",w="",t="";
private Roadpanel rpanel;
private Controlew ew;
Date date;
public Controlns(Roadpanel rp,Controlew ew)
{
rpanel=rp;
this.ew=ew; 
}
public void run()
{
out:while(true)
{
synchronized(ew)
{
if(ew.v)
{
try{
ew.wait();
}catch(Exception e)
{e.printStackTrace();}
}
rpanel.n1.setForeground(Color.green); 
                     … //设置信号灯的颜色
rpanel.w.setForeground(Color.red);
tp=Integer.parseInt(rpanel.time.getText());
if(tp!=0)
{n=tp+1;}
else
{n=11;}
t.indexOf(n);
rpanel.n.setText(t);
rpanel.s.setText(t);
rpanel.e.setText(t);
rpanel.w.setText(t);
while(true)
{ 
date=new Date();
rpanel.d.setText(date.toString());
try{
Thread.currentThread().sleep(1000);
}catch(Exception e){}
if(n==4)
{
rpanel.n2.setForeground(Color.yellow);
… ////设置信号灯的颜色,最后4秒为黄灯。
rpanel.s1.setForeground(Color.yellow); 
}
else
if(n==1)
break;
n--;
if(n<10)
s="0"+s.valueOf(n);
else
s=s.valueOf(n);
rpanel.n.setText(s);
rpanel.s.setText(s);
rpanel.e.setText(s);
rpanel.w.setText(s);
ew.v=true;
ew.notify(); 
} 
} 
} 
}
}

从上面的代码中,我们可以看出,南北信号灯控制线程是否进入阻塞状态,刚好和东西信号灯控制线程相反:

if(ew.v){      //使用ew 对象的锁
try{
ew.wait();
}catch(Exception e)
    {e.printStackTrace();}
}

   也就是说,在一个车辆放行周期内(如10秒),先由Controlew控制,Controlns阻塞。待周期结束后,Controlew阻塞,Controlns控制,以后反复交替。

   

2)车辆的随机添加和移出

为了实现对场内车辆的控制,我们在Roadpanel的实例中,设置了基于Amino组件的LockFreeList实体carlist,如下:

LockFreeList<Car> carlist=new LockFreeList<Car>();

LockFreeList实际上是一个线程安全的Amino并发组件,用来存放整个街道所有车辆。在不同的线程中,可以安全地对去做移出、添加等操作,有效地解决了数据冲突等问题。

在EnterRoad类中,我们对carlist的操作如下:

     

rp.carlist.add(cart);   // rp就是Roadpanel的实例,cart是车辆类实例。
        cart.start();             // cart本身也是一个线程

    在ContralCarList类中,如果一个车辆类线程已经结束,我们将从carlist移出该实例:

class ContralCarList implements Runnable{
LockFreeList<Car> carlist;
public ContralCarList(LockFreeList<Car> carlist){
this.carlist=carlist;
}
   public void run(){  
  while(true)
  {
  try{  
Thread.currentThread().sleep(500l);
    }catch(Exception e){}
  for(Car car:carlist){
if( ! car.equals(null) ){
if( ! car.isAlive() ){
carlist.remove(car);
  }
}
} 
  }
   }
}

对LockFreeList的操作还有几处,由于采用了Amino组件,有效地解决了数据冲突的问题。

3)车辆对红绿灯信号的响应

    为了让车辆能够对红绿灯有相应,在红绿灯信号变换的时候,我们设计了一个Four类:

class Four {
   static int w1=1;   
   static int s1=1;
   static int e1=1;
   static int n1=1;
}

其中设置了四个类变量,用它们来表示信号的变化:

   当东西放行、南北阻塞时:w1=e1=1; s1=n1=0;

   当东西阻塞、南北放行时:w1=e1=0; s1=n1=0;

在Car类中,我们设计了一个整形量,表示车辆的位置:

int fromto;   // 1=从西往东 2=从南往北  3=从东到西  4=从北往南

       同时利用街道各段路的长度,可以有效地模拟车辆的移动或停止:

if( fromto == 1 )  // w
if(x<=250)
x=x+d1*Four.w1;
else
x=x+d1;
if( fromto == 3 )  // e
if(x>500)
x=x-d1*Four.e1;
else
    x=x-d1; 
if( fromto == 2)   // s
if(y>420)
y=y-d1*Four.s1;
else
y=y-d1; 
if( fromto == 4 )  // n
if(y<200)
y=y+d1*Four.n1;
else
y=y+d1;

其中,d1为车辆的移动增量。

4)整体动画显示的质量控制

    如果对每个Car实例,均调用paint()绘图,会发现图形界面闪烁得非常厉害。为了实现良好的动画质量,我们设计了单独的线程来刷新界面.

class Freshpanel implements Runnable{
  private Roadpanel rp; 
  public Freshpanel(Roadpanel rpanel){ 
  rp=rpanel;
  }
    public void run(){
  while(true){
      rp.repaint();
       try {
Thread.sleep(50);
  } catch (InterruptedException e) {
      e.printStackTrace();
    }
}
     }  
}

   

1.4 数据冲突和死锁检测

在本案例中,我们采用Mtrat检测数据冲突和死锁的问题,检测的结果界面如图6:

图6 Mtrat检查

     从上图中,可以看出程序中没有发现数据冲突和死锁的问题。

1.5 总结

在本案例演示了十字路口车辆运行的简单状况,用Java多线程技术和IBM提供的并发组件Amino实现在有交通信号灯控制情况下多车辆的运行的简单控制。

从程序运行的效果来看,程序运行正常,各线程协调工作准确无误,达到了程序设计的目的,同时,也证明了Amino组件的使用性和正确性。

但是模拟交通信号控制下路口的交通情况,本身是一件较复杂的事情,有很多工作可以继续深入做下去:

1) 实现车辆准确的显示,主要是长、宽不同。

2) 实现车辆运行精细化的控制,如红灯前,有些车辆还没有达到停止线,车辆还可以继续运行。中心交汇区,检查车辆的碰撞情况,并提出相应得算法。

3) 设计“左转弯”和“右转弯”车辆

由于本案例主要目的是验证多线程情况下Amino 组件的工作情况,所以上面的工作没有进一步深入。这也是本案例的一些不足之处。