邱俊的空间

Simple is beautiful.
posts - 15, comments - 61, trackbacks - 0, articles - 4
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

公告

2008年12月30日


作者: abruzzi  链接:http://abruzzi.javaeye.com/blog/266335  发表时间: 2008年11月08日

声明:本文系JavaEye网站发布的原创博客文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

  • 用途及用法

 

网络请求通常有两种形式:第一种,请求不是很频繁,而且每次连接后会保持相当一段时间来读数据或者写数据,最后断开,如文件下载,网络流媒体等。另一种形式是请求频繁,但是连接上以后读/写很少量的数据就断开连接。考虑到服务的并发问题,如果每个请求来到以后服务都为它启动一个线程,那么这对服务的资源可能会造成很大的浪费,特别是第二种情况。因为通常情况下,创建线程是需要一定的耗时的,设这个时间为T1,而连接后读/写服务的时间为T2,当T1>>T2时,我们就应当考虑一种策略或者机制来控制,使得服务对于第二种请求方式也能在较低的功耗下完成。

通常,我们可以用线程池来解决这个问题,首先,在服务启动的时候,我们可以启动好几个线程,并用一个容器(如线程池)来管理这些线程。当请求到来时,可以从池中去一个线程出来,执行任务(通常是对请求的响应),当任务结束后,再将这个线程放入池中备用;如果请求到来而池中没有空闲的线程,该请求需要排队等候。最后,当服务关闭时销毁该池即可。

  • 结构

线程池中通常由这样几个概念(接口)组成:

  1. 线程池(Thread pool ),池是一个容器,容器中有很多个执行器,每一个执行器是一个线程。当然,这个容器的实现,可以是链表,可以是数组等等,不需要关心,需要关心的是,池必须提供一个可以从中取出执行器 的方法,可能还需要一个池中现有活动线程数方法,销毁池的方法等。
  2. 执行器(Executor ),每个执行器是一个线程,每个执行器可以执行一个任务 ,任务是做什么,此时还不很明确,它需要提供任务的setter/getter方法,并且作为一个线程,他可以独立运行,执行器执行完自身后,需要将自身放入池中。
  3. 任务(Task ),任务是每个线程具体要做的事,如资源下载,播放flash片段,打印一段文字到控制台等等,它本身不能执行,而需要将自身交给执行器。

整个池的机制和结构就是这样,当然,需要一个调度者(scheduler)来协调主线程和池的关系。结构,或者接口的目的是为了让我们从细节中解脱出来,从一个比较抽象的层次来描述系统,这样的好处是简单,而且设计出来的框架比较通用,可以适应很多相近相似的情况。由于Task具体干什么我们不知道,所以它几乎可以干任何适应于上边总结的网络连接的第二种情况(T1>>T2)。

  • 类的结构图

虽然为一个简单的实现设计一个标准的UML视图是不太现实的,但是这是一种受鼓励的做法,至少应该用铅笔在草纸上画出相关的视图,这样可以帮助以后的维护和更高级的扩展。

frame

  • 线程池的简单实现

实现可以是通过多种语言的,我们在此选择面向对象的JAVA,而如果你使用C的话,也没有问题,问题在上一小节已经描述清楚,语言是不重要的。

池是一个容器,我们考虑使用java.util.LinkedList类(可能由于它的长度是可变的,而且不需要我们使用者来考虑),也就是说,池需要维护一个链表。

 


public interface Pool {//池接口
    Executor getExecutor();
    
void destroy();
}

public interface Executor {//执行器接口
    void setTask(Task task);
    Task getTask();
    
void startTask();
}

 

 

 

鉴于执行器是池中的对象,而且外部没有必要知道其细节,我们考虑将Executor接口的实现做为Pool接口的实现的内部类。这样做的另一个好处是,更便于池的管理。

 

 

import java.util.LinkedList;
import java.util.Properties;

import redesigned.utils.PropReader;

public class ThreadPool implements Pool{
    
private boolean isShut;
    
private LinkedList pool;
    
private static Properties prop = PropReader.getProperties("webconfig.properties");
    
private int size = Integer.parseInt(prop.getProperty("threadsperpage""3"));
    
public ThreadPool(){
        
// read configuration and set the
        
// content of pool by objects of Executor
        isShut = false;//set the status of pool to active
        pool = new LinkedList();
        
for(int i = 0; i < size; i++){
            Executor executor 
= new ExecutorImpl();//new a executor thread
            pool.add(executor);//add it to pool
            ((ExecutorImpl)executor).start();//start it
        }
    }
    
public void destroy() {//Destroy
        synchronized(pool){
            isShut 
= true;//set the status of pool to inactive
            pool.notifyAll();//notify all listener.
            pool.clear();//clear the list of threads
        }
    }

    
public Executor getExecutor(){
        Executor ret 
= null;
        
synchronized(pool){//return if any.
            if(pool.size() > 0){
                ret 
= (Executor)pool.removeFirst();
            }
else{
                
try {
                    pool.wait();
                } 
catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ret 
= (Executor)pool.removeFirst();
            }
        }
        
return ret;
    }

 

好了,池设计好了,再来看看任务(Task)的接口和实现

public interface Task {//这个接口也比较简单,可以执行,可以取到执行结果
    void execute();
   
Object getResult();
}
 

 

Task的实现可以是多种多样的,下边的例子是一个加载资源的Task.使用方式

 

Pool pool = new ThreadPool();// new a ThreadPool
//load resources on each page, and start #s of thread.
for(int i = 0; i < resourceList.size();i++){
    Executor executor 
= pool.getExecutor(); // get Executor form pool
    Task resourceLoader = new ResourceLoader((String)resourceList.get(i));
    executor.setTask(resourceLoader); 
// set the task to executor
    executor.startTask(); // try to start the executor.
}

//wait while all task are done, the destroy the pool.
pool.destroy(); 

  • 优势,或者适用范围
  1. 在并发时,需要被并发的线程不需要知道自己什么时候需要被启动,它子需要考虑这样一种情况:它自己从一个地方取出来一个执行器,然后把任务交给执行器,然后等待执行器结束即可,他关心的是自己所需要干的事,或者自己负责的事。这样,大家都简单,因为只需要做好自己的事情就好了。面向对象的一个秘诀为:永远相信合作者,使用别人的接口而不是自己去实现所有的接口。
  2. 这种T1>>T2的请求方式在网络中固然是常见的,在实际问题中同样是常见的。因此,掌握这种模式可能会对我们以后的程序设计提供方便和好处。
  • 小结

同步问题: 同步在线程的并发中意义非常之大,对临界资源的控制是并发时最关键的地方。如在线程池中,当池中没有空闲的线程时,新来的请求就必须等待,而一旦一个Task运行结束后,一方面将自己放入池中,一方面需要通知等待在pool中的其他线程。每一个执行器线程,一开始启动,则进入等待状态,此时不会消耗CPU资源。而当在外部调用执行器的startTask()方法,即可通知线程从等待状态中醒来,去出Task,执行之,将执行器本身放入池中,然后继续等待。

 

当然,实现的策略是可以多种多样的,但是问题的本质已经在第二小节结构 很明确的被定义了。

 

最近开始学习JAVA,同时也开始熟悉面向对象的思维方式,这篇日志,一来作为一个备忘,二来可以对可能需要的人提供帮助。以上可以算作是小结。

posted @ 2008-12-30 21:08 abruzzi 阅读(180) 评论(0) 编辑


作者: abruzzi  链接:http://abruzzi.javaeye.com/blog/266027  发表时间: 2008年11月12日

声明:本文系JavaEye网站发布的原创博客文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

事件监听器模式(一个简单的例子)


在GUI程序设计中,在设计时通常不会知道用户的动作什么时候会发生,如用户可能点击某一
个按钮,也可能不点击,而且点击按钮的时机在运行时才可能知道。如果用传统的程序控制
方式,则很难做到准确的响应。通常这种情况或者类似的情况下,我们可以使用事件/监听器模式。

将一个监听器(listener)与特定的控件(如按钮等)绑定起来,当发生用户点击等事件(Event)时,
调用监听器的处理方法,从而响应用户的动作。是为事件/监听器模式。

事件有很多种,而且事件发生的时序是不确定的。可以简单的定义一个事件的接口,如下例:

interface Event{
    
static int event1 = 0;
    
static int event2 = 1;
    
static int event3 = 2;
    ...
    
int getEventType();
}


一个监听器的接口的一种可能形式:

interface listener{
    
void handleEvent(Event event);
}

 


实现监听器接口的类可以根据事件的类型作出具体的响应方式如:

class ListenerImpl implements Listener{
some other methods of 
this implementation;
...
void handleEvent(Event event){
    
switch(event.getEventType()){
        
case Event.event1:
            doSomething();
            
break;
        
case Event.event2:
            doSomethingElse();
            
break;
        
default:
            
break;
    }
}

GUIThread thread 
= new GUIThread();
thread.setListener(
this);//注册监听器
...
}

 


在GUI的主流程中,需要有一个Event的实现者,和一个发起事件的组件,如:

 

class EventImpl implements Event{
    
private int type;
    
public EnentImpl(int type){
        
this.type = type;
    }
}

class GUIThread{
    
private Listener listener;
    
public void setListener(Listener listener){
        
this.listener = listener;
    }
   
    
public void execute(){
        
if(this.listener != null){
            Event event 
= new EventImpl(type);//触发了某事件
            listener.handleEvent(event);
        }
    }
}


 

当然,这个实现只是一个原理性的描述,应该可以很容易根据这个描述作出自己的实现。关于事件的处理以及事件类型的定义等可以完全根据自己的需求定制。

posted @ 2008-12-30 21:08 abruzzi 阅读(102) 评论(0) 编辑


作者: abruzzi  链接:http://abruzzi.javaeye.com/blog/266317  发表时间: 2008年11月12日

声明:本文系JavaEye网站发布的原创博客文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

  • 内部机制

打印机内部设有缓冲区,当有新任务来到的时候,打印机只是简单的从任务中取出需要打印的消息,然后将其存入自身的缓冲区,然后返回,具体的打印任务交给一个线程来处理,打印线程从缓冲区中读消息,打印,然后等待,直到有别的线程唤醒它。其结构如图所示:

 

 

  • 单例模式

作为一个系统硬件的模拟,在一个项目中有一个打印机就够用了,在项目中任何需要打印消息的地方,需要使用SyncPrinter.getInstance() 静态方法获取此刻的SyncPrinter实例,并使用print(String message)进行打印。(关于单例模式的细节可以参考别的设计模式的书籍)

  • 同步打印

当然,打印的线程不需要等待打印机缓慢的打印结束,另一种做法是:当打印机收到打印任务后,将此任务放入自己的缓冲区,然后迅速返回,调用打印机的线程可以立即开始接下来的动作,而同时,打印机可以另起一个线程,来打印存储在自己缓冲区中的数据,从而做到同步打印。

缓冲区在本例中实现为一个队列(一个先进先出的数据结构FIFO),队列中的数据总是从尾部插入,从头部被取出。

  • 实现

本例使用JAVA语言实现,当然,任何其他支持线程的语言也可以完成这个任务。

import java.util.LinkedList;
import java.util.List;

public class SyncPrinter {
    
private static SyncPrinter instance;
    
private List msgQueue;
    
private PrintWorker printWorker;

    
public static SyncPrinter getInstance(){//单例模式,任何时候系统中只有一个类实例
        synchronized(SyncPrinter.class){
            
if(instance == null){
                instance 
= new SyncPrinter();
            }
        }
        
return instance;
    }

    
private SyncPrinter(){
        msgQueue 
= new LinkedList();
        printWorker 
= new PrintWorker();
        printWorker.start();
    }

    
public void print(String message){// 对外公开的使用打印机的接口
        synchronized(msgQueue){
            msgQueue.add(message);
            msgQueue.notify();
        }
    }

    
private class PrintWorker extends Thread{//一个执行打印任务的内部类的封装
        private boolean loop = true;
        
private Object lock = new Object();

        
public void stopPrinter(){
        }

        
public void run(){
            
while(loop){
                String msg 
= "";
                
synchronized(msgQueue){
                    
try {
                        msgQueue.wait();
                    } 
catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                
// IO
                msg = (String)msgQueue.remove(0);
                
if(msg != null){
                    System.out.println(msg);
                }
            }
        }
    }
// 对于这个死循环的控制可以做在外部,也可以做在这个类中,通过一个方法来控制,如stopPrinter()
}

 

  • 小结

这个打印机的意义或许不是很大,但是让快速的线程等待一个缓慢的IO过程是不合理的,同时,这是一种分工的思想,而这种互不干涉,各司其职的做法正是面向对象的核心。

借此文来对面向对象的设计原则做一个巩固,同时也可能会帮助其他需要使用同步打印机的人。

 

 

 

posted @ 2008-12-30 21:08 abruzzi 阅读(84) 评论(0) 编辑


作者: abruzzi  链接:http://abruzzi.javaeye.com/blog/266677  发表时间: 2008年11月13日

声明:本文系JavaEye网站发布的原创博客文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

维基(wiki)中关于自然选择的词条的搜索结果如下:

自然选择(Natural selection)也称为天择。指生物的遗传特征在生存竞争中,由于具有某种优势或某种劣势, 因而在生存能力上产生差异,并进而导致繁殖能力的差异,使得这些特征被保存或是淘汰。

基因是遗传特征的基础,也是自然选择的单位,自然选择则是演化的主要机制。经过自然选择而能够称成功生存, 称为“适应”;当一个物种中的不同族群因为自然选择而产生生物分类学上的差异时,则称为“物种形成”; 若是族群因为不受自然选择青睐而导致族群规模缩小进而消失,则称为“灭绝”。

这个理论最早是由查尔斯·达尔文在1859年出版的《物种起源》中提出,并因此广为人知。这个观念也衍生出一些特例。 例如性选择(简称性择)解释有性生殖生物的某些遗传特征得以保存的原因;人工选择(简称人择)则将自然选择概念 应用在受人类圈养的生物上,例如家畜、宠物与农作物的育种。此外自然选择中,与性选择、人工选择无关的部分, 则称为生态选择。

 

自然选择的因素
* 过度繁殖。
* 遗传和变异。
* 生存斗争,由个体之间的斗争和于无机环境之间的斗争造成。
* 适应。

遗传算法(Genetic Algorithm)由密歇根大学的约翰·霍兰德(John Holland)和他的同事于二十世纪六十年代在对细胞自动机(cellular automata)进行研究时率先提出。是一种全局优化算法,应用领域比较广泛。

遗传算法借鉴了达尔文的进化论,也使用了一些达尔文定义的术语,如选择,遗传,变异,适应度等。当然,也引入了一些后来自然科学家提出的概念,如基因,DNA,染色体等。

 

好和坏的区别:
如果一个个体适应自身所在的环境,则此个体为“好”,反之为“坏”。在遗传算法中,这个概念由“适应度”来表示。当然,适应度跟具体要解决的问题有关。通常,适应度计算函数会返回一个0-1之间的浮点数。

 

进化论的本质:

写道
物竞天择,优胜劣汰!

 遗传算法的自然语言描述:

  1. 从一个种群中随机的取出一定数量的个体,用以进化计算
  2. 对这些个体进行选择,遗传,变异等操作
  3. 计算个体的适应度,并排序,选择出下一次迭代的个体,淘汰适应度较低的一些个体
  4. 如果选择出的个体已经足够“好”,则停止计算,否则继续1-3的操作

最后计算的结果即为最优解集合。

 

  1. 染色体
    一个染色体上可以有很多个DNA,每个DNA上有很多个基因,基因可以通过交叉,变异等操作发生改变,染色体是遗传算法的基本单元,一个染色体可以是一个一维的数组,数组中的每一个单元都是一个基因,因此,染色体被实现成串类型。
  2. 基因
    如一个染色体中STRING,包含有五个基因,STRING[] = {"X","Y","Z","W","Q"},基因STRING[0]的等位基因为X,STRING[1]上的等位基因为Y等。
  3. 选择
    复制操作,将上一级(父)的染色体复制给下一级(子),此为遗传,通过遗传操作可以将父代的优秀的,适应环境的因素传递给子代,但是不发生改变
  4. 交叉
    发生部分改变,将两个染色体的一部分互换,即为交叉。当然,从种群的角度来看,交叉同样不影响种群中的基因情况。但是对于个体来说,交叉操作可能会产生比较优秀的染色体。比如,一个染色体SA含有两个不利于适应的基因,SA[3]和SA[4],但是染色体SB含有两个利于适应环境的基因SB[3]和SB[4],此时如果SA和SB发生交叉操作,且交叉位置为3,则SA可以提高自己的适应度,从而加快迭代的结束。
  5. 变异
    变异,即基因上的等位基因发生改变,这种改变是随机的,不确定的。至于这种改变是否是“好”的,需要靠适应度函数来确定。
  6. 适应度(fitness)
    个体对环境的适应程度叫做适应度(fitness)。为了体现染色体的适应能力,引入了对问题中的每一个染色体都能进行度量的函数,叫适应度函数.

 

 

由于操作之前,个体对自己将来会进化成什么样的结果是一无所知的,所以需要有一个结束点,一个度量方式。当然,自然界是没有结束点的,生物只能说适应了目前的环境,但是它会一直进化,直到适应了新的环境或者灭绝。

 

一个基于JAVA的遗传算法的实现是JGAP(Java Genetic Algorithm Package)。参见:http://jgap.sourceforge.net/

 

遗传算法就是遍历搜索空间或连接池,从中找出最优的解。搜索空间中全部都是个体,而群体为搜索空间的一个子集。
并不是所有被选择了的染色体都要进行交叉操作和变异操作,而是以一定的概率进行,一般在程序设计中交叉发生的概率要比变异发生的概率选取得大若干个数量级。大部分遗传算法的步骤都很类似,常使用如下参数:

 

  • Fitness函数
    这是与具体实现有密切关系的一个参数,通常需要自己实现,定义“好”与“坏”的界限
  • 适应度阀值
    适合度中的设定的阀值,当最优个体的适应度达到给定的阀值,或者最优个体的适应度和群体适应度不再上升时(变化率为零),则算法的迭代过程收敛、算法结束。否则,用经过选择、交叉、变异所得到的新一代群体取代上一代群体,并返回到选择操作处继续循环执行。
  • 交叉概率
    在循环中进行交叉操作所用到的概率。交叉概率一般取0.6至0.95之间的值,Pc太小时难以向前搜索,太大则容易破坏高适应值的结构。
  • 变异概率
    从个体群中产生变异的概率,变异概率一般取0.01至0.03之间的值变异概率Pm太小时难以产生新的基因结构,太大使遗传算法成了单纯的随机搜索。
  • 个体的数目
    有定长和变长两种。它对算法的性能也有影响。由于GA是一个概率过程,所以每次迭代的情况是不一样的,系统参数不同,迭代情况也不同。

JGAP对很多参数设置进行了隐藏,封装。使用者只需要提供自己的Fitness函数即可,当然如果你对GA比较熟悉的话,可以对这些隐式参数进行修改。

 

JGAP中自带的例子中有这样一个例子:MinimizingMakeChangeFitnessFunction.java,我们下来就以这个例子来说一说这个GA包的用法。

  1. 设计自己的染色体(Chromosome)
  2. 实现适应度函数
  3. 设置Configuration 对象
  4. 建立一个种群(可能的解集)
  5. 开始进化(运行遗传算法)

下面是一个例子,我删除了一些注释和版权信息等,并做了一些简单的格式化

package examples;

import org.jgap.*;

public class MinimizingMakeChangeFitnessFunction
    
extends FitnessFunction {

  
private final int m_targetAmount;

  
public static final int MAX_BOUND = 4000;

  
public MinimizingMakeChangeFitnessFunction(int a_targetAmount) {
    
if (a_targetAmount < 1 || a_targetAmount >= MAX_BOUND) {
      
throw new IllegalArgumentException(
          
"Change amount must be between 1 and " + MAX_BOUND + " cents.");
    }
    m_targetAmount 
= a_targetAmount;
  }

  
public double evaluate(IChromosome a_subject) {
    
boolean defaultComparation = a_subject.getConfiguration().
        getFitnessEvaluator().isFitter(
21);
        
    
int changeAmount = amountOfChange(a_subject);
    
int totalCoins = getTotalNumberOfCoins(a_subject);
    
int changeDifference = Math.abs(m_targetAmount - changeAmount);
    
double fitness;
    
if (defaultComparation) {
      fitness 
= 0.0d;
    } 
else {
      fitness 
= MAX_BOUND/2;
    }

    
if (defaultComparation) {
      fitness 
+= changeDifferenceBonus(MAX_BOUND/2, changeDifference);
    } 
else {
      fitness 
-= changeDifferenceBonus(MAX_BOUND/2, changeDifference);
    }

    
if (defaultComparation) {
      fitness 
-= computeCoinNumberPenalty(MAX_BOUND/2, totalCoins);
    } 
else {
      fitness 
+= computeCoinNumberPenalty(MAX_BOUND/2, totalCoins);
    }

    
return Math.max(1.0d, fitness);
  }

  
protected double changeDifferenceBonus(double a_maxFitness,
                                         
int a_changeDifference) {
    
if (a_changeDifference == 0) {
      
return a_maxFitness;
    } 
else {
      
if (a_changeDifference * a_changeDifference >= a_maxFitness / 2) {
        
return 0.0d;
      } 
else {
        
return a_maxFitness / 2 - a_changeDifference * a_changeDifference;
      }
    }
  }

  
protected double computeCoinNumberPenalty(double a_maxFitness, int a_coins) {
    
if (a_coins == 1) {
      
return 0;
    } 
else {
      
return (Math.min(a_maxFitness, a_coins * a_coins));
    }
  }

  
public static int amountOfChange(IChromosome a_potentialSolution) {
    
int numQuarters = getNumberOfCoinsAtGene(a_potentialSolution, 0);
    
int numDimes = getNumberOfCoinsAtGene(a_potentialSolution, 1);
    
int numNickels = getNumberOfCoinsAtGene(a_potentialSolution, 2);
    
int numPennies = getNumberOfCoinsAtGene(a_potentialSolution, 3);
    
    
return (numQuarters * 25+ (numDimes * 10+ (numNickels * 5+
        numPennies;
  }

  
public static int getNumberOfCoinsAtGene(IChromosome a_potentialSolution,
                                           
int a_position) {
    Integer numCoins 
=
        (Integer) a_potentialSolution.getGene(a_position).getAllele();
    
return numCoins.intValue();
  }

  
public static int getTotalNumberOfCoins(IChromosome a_potentialsolution) {
    
int totalCoins = 0;
    
int numberOfGenes = a_potentialsolution.size();
    
    
for (int i = 0; i < numberOfGenes; i++) {
      totalCoins 
+= getNumberOfCoinsAtGene(a_potentialsolution, i);
    }
    
return totalCoins;
  }
}

 

FitnessFunction 是一个抽象类,MinimizingMakeChangeFitnessFunction集成该类,并对evaluate方法给出具体实现,这个方法就是所谓的适应度函数。

 

确定染色体的结构及基因

第一步,决定“染色体”的结构和性质,如染色体包含多少个基因,以及这些基因有什么表现形式(如人类基因记载了人的肤色,面貌特征等信息)。这个例子中,程序会生成一堆零钱,这些零钱包含了一些种类的美国硬币,而钱数正好与用户的输入向匹配。这个例子中,染色体的表现形式就是一堆硬币。每个染色体中有四个基因(15分,10分,5分和1分)

 

这个染色体可以看作是一个串:STRING[4] = {"quarter","dime","nickel","pennie"},第一个位置上的数目为3(等位基因),则表示15×3 = 45,第二个位置上为5(等位基因),则表示10×5 = 50分,等等。

 

 

适应度函数的实现---什么算“好”

在这个例子中,我们认为,硬币的总数目恰好等于用户指定的数目(如85,34等),而且硬币的数目尽可能的少,想必学习过算法设计与分析课程的有点熟悉吧(贪婪算法的一个例子就是这个大名鼎鼎的“找钱问题”)。

 

在就是一些处理字符串,计算总数之类的方法,总的没有什么难以理解的地方,就不消多说了,如果还不明白可以留言,我会耐心回答的,呵呵。

 

总之:在使用JGAP时,关键问题有两点:

  1. 确定染色体,以及基因的分配
  2. 确定适应度函数(描述如何区分好坏)

 小结:

遗传算法的应用领域还是比较广泛的,不过解决的问题可以统称为全局优化相关的问题 。大家可以在参考文献给出的链接中获取更多的信息。

 

参考文献:

 

中文维基:http://zh.wikipedia.org/w/index.php?title=%E8%87%AA%E7%84%B6%E9%80%89%E6%8B%A9&variant=zh-cn

IBM developeWorks:http://www-128.ibm.com/developerworks/cn/java/j-lo-robocode1/index.html

JGAP文档:http://jgap.sourceforge.net/doc/tutorial.html

posted @ 2008-12-30 21:08 abruzzi 阅读(590) 评论(0) 编辑


作者: abruzzi  链接:http://abruzzi.javaeye.com/blog/285034  发表时间: 2008年12月02日

声明:本文系JavaEye网站发布的原创博客文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

Java的远程方法调用(Remote Method Invocation) 是为了分布式计算而提出来的,最近做一个项目需要用到,所以学习了一番,现在将一个简单的demo贴出来,以便想要学习RMI的同志可以快速上手。

RMI的调用是基于接口的,这个接口的定语需要客户知道,客户程序运行时需要一个实现该接口的类的存根(stub)。RMI的内部使用了TCP/IP连接方式,因此需要一个安全机制,且需要对客户机的权限进行一定的设置。

想要被远程调用的接口需要扩展Remote类,接口中定义的方法需要对RemoteException异常进行处理,当然,也可以只是抛出异常,将对异常的处理延迟到别的类。RMI的安全处理机制使用的是RMISecurityManager,这个类提供必要的安全验证等处理,而权限控制是通过在客户端和服务端分别设置policy文件来做到的。下面给出一个比较简单,但是能说明问题的例子,略去了不必要的代码,而且这个程序可以通过扩展达到你的某些特殊要求。

这个例子是这样,设计一个接口TaskContainer,这个接口对LinkedList进行了一个简单的封装,用途是作为Task的容器,其中可以加入多个Task,这些Task又分别可以独立运行(运行的逻辑可以自行扩展)。TaskContainer扩展了Remote类,因此是可以通过远程调用的。

 

 下面是这个接口的实现

package rmidemo;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.LinkedList;

public class TaskContainerImpl extends UnicastRemoteObject 
implements TaskContainer{

    
private static final long 
            serialVersionUID 
= 4656230894967394376L;
    LinkedList
<Task> taskList;
    
    
public TaskContainerImpl() throws RemoteException{
        taskList 
= new LinkedList<Task>();
    }
    
    
public Task getTask() throws RemoteException{
        
return taskList.size() > 0 ? 
                taskList.removeFirst() : 
null;
    }

    
public int getTaskCount() throws RemoteException{
        
return taskList.size();
    }

    
public void addTask(Task task) throws RemoteException{
        taskList.add(task);
    }
}

 代码非常简单,就不用多说了,需要注意的是这个类需要扩展UnicastRemoteObject类,而且每个方法都要对RemoteException异常进行处理,这里只是简单的抛出。

 

下面是引用到的Task接口和其实现

package rmidemo;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Task extends Remote{
    
void execute() throws RemoteException;
    
int getTaskID() throws RemoteException;
}

 Task也很简单,一个执行方法,一个获得Task的ID的方法,实现:

package rmidemo;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class TaskImpl extends UnicastRemoteObject implements Task{
    
private static final long serialVersionUID = -4520397655735981164L;
    
private int taskID;
    
    
protected TaskImpl(int i) throws RemoteException {
        taskID 
= i;
    }

    
public void execute() throws RemoteException{
        System.out.println(
"execute task = "+taskID);//这里只是一个简单的打印,你可以任意扩展之
    }
    
    
public int getTaskID() throws RemoteException{
        
return taskID;
    }
}

 定义完这些具体的逻辑,就可以在server端进行RMI的配置了,下面是RMI的主入口TaskServer

package rmidemo;

import java.rmi.Naming;
import java.rmi.RMISecurityManager;
import java.rmi.registry.LocateRegistry;

public class TaskServer {
    
private static final String host = "localhost";
    
private static final int port = 9527;
    
    
public static void main(String[] args){
        System.setProperty(
"java.security.policy"
        
"TaskServer.policy");//权限控制
        
        
if(System.getSecurityManager() == null){
            System.setSecurityManager(
new RMISecurityManager());
        }
//安全管理器的安装
        
        
try{
            System.out.println(
"Constructing server");
            LocateRegistry.createRegistry(port);
//注册
            
            TaskContainer container 
= new TaskContainerImpl();
            
            container.addTask(
new TaskImpl(1));//想容器中添加几个Task
            container.addTask(new TaskImpl(2));
            container.addTask(
new TaskImpl(3));
            
            System.out.println(
"binding server impl to registry");

            Naming.rebind(
"//"+host+":"+port+"/TaskContainer", container);
            
        }
catch(Exception e){
            e.printStackTrace();
        }
    }
}

 先安装一个权限配置文件,再安装一个安全管理器,然后注册监听端口,并创建需要被远程调用的对象container,并通过Naming.rebind(String url, Object remote)方法对其绑定,以便远程对其按照URL进行访问。

 

权限的配置需要放在一个单独的文件中,这个例子中,配置放在TaskServer.policy文件中,其内容如下:

grant
{
 permission java.net.SocketPermission
      
"*:1000-9999","accept,connect,listen,resolve";
};

 以上为服务器端的全部配置,不过,现在还不能使用,不是因为没有客户端,而是RMI需要一个存根stub才能使远程的线程来调用。

 

JDK里带了一个工具rmic.exe,这个工具可以根据扩展了Remote的类的class文件生成一个存根类,如根据x.class,生成x_stub.class。如果你的类有包名的话,你需要提供给rmic一个完整的包路径。

 

通过下面的命令即可完成这个动作:

$ rmic rmidemo.TaskContainerImpl
$ rmic rmidemo.TaskImpl

 现在在类的完整的路径下生成两个文件TaskContainerImpl_stub.class,TaskImpl_stub.class,服务端的所有配置到此结束。下面我们来看看客户端的代码。

 

相比服务端,客户端的代码就相当简单了,只需要一个用于测试的类即可:

package rmidemo;

import java.rmi.Naming;
import java.rmi.RMISecurityManager;

public class TaskClient {
    
private static final String host = "localhost";
    
private static final int port = 9527;
    
    
public static void main(String[] args){
        System.setProperty(
"java.security.policy"
        
"TaskClient.policy");//install the policy file
        
        
//install RMI Security Manager
        System.setSecurityManager(new RMISecurityManager());
        String url 
= "rmi://"+host+":"+port+"/";
        
        
try{
            TaskContainer container 
= 
                (TaskContainer)Naming.lookup(url
+"TaskContainer");
            
while(container.getTaskCount() > 0){
                container.getTask().execute();
//invoke the remote method.
            }
        }
catch(Exception e){
            e.printStackTrace();
        }
    }
}

 同样,客户端也需要一个权限配置文件,TaskClient.policy

grant
{
 permission java.net.SocketPermission
      
"*:1000-9999","connect";
};

 好了,客户端的代码及配置到此结束。现在,我们可以运行这个典型的RMI了,为了清晰起见,我们在控制台上运行这两个程序(分别启两个cmd窗口,因为RMI的server端不会退出[CTRL+C]),先启动server端,再运行客户端,可以看到,server窗口的输出为:

Constructing server
binding server impl to registry
execute task 
= 1
execute task 
= 2
execute task 
= 3

 而客户端没有人户输出就结束了,这正好说明了RMI的意义和流程,client调用的是远程的方法(即server端的对象的方法)

 

好了,这个简单的例子就说到这里,程序很简单,我就不打包了。如果有任何问题,可以留言。

posted @ 2008-12-30 21:08 abruzzi 阅读(301) 评论(1) 编辑


作者: abruzzi  链接:http://abruzzi.javaeye.com/blog/289441  发表时间: 2008年12月09日

声明:本文系JavaEye网站发布的原创博客文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

首先说一下,这个例子不是我原创的,例子本身来自《JavaScript权威指南》(4th Edition),之所以放在这里,实在是因为这个js脚本太牛B了,如果有正则表达式的基础,你可以很轻松的实现各种各样的js验证器,这个js脚本只是提供一个通用的验证框架,具体的验证规则,以及不能通过验证的时候所产生的动作完全可以定制。好了,言归正传。

 

首先,给需要验证的表单(Form)中的需要加入验证器的input控件加入一定的验证规则,这个例子中是通过给Input控件加入一个自定义的属性来实现的,如下例:

 

<script src="validate.js" ></script>
<style>
    input.invalid
{background: #faa;}
    input.valid
{background: #afa;}    
</style>

<form>
    name : 
<input type="text" name="name" required><br>
    email: 
<input type="text" name="email" pattern="^\s*\w+@\w+\.\w+\s*$"><br>
    zipcode : 
<input type="text" name="zipcode" pattern="^\s*\d{5}\s*$"><br>
    unvalidate : 
<input type="text"><br>
    
    
<input type="submit" value="submit query">
</form>

 

 当你看到required或者pattern时,不要慨叹自己才疏学浅,因为这两个属性是自定义的。从而实现了js脚本跟html的分离。下面是这个牛B的JavaScript脚本:

(
function(){
    
if(window.addEventListener) window.addEventListener("load", init, false);
    
else if(window.attachEvent) window.attachEvent("onload", init);
        
    
function init(){
        
for(var i = 0;i < document.forms.length;i++){
            
var f = document.forms[i];
            
var needsValidation = false;
            
for(var j = 0;j < f.elements.length; j++){
                
var e = f.elements[j];
                
if(e.type != "text"continue;
                
var pattern = e.getAttribute("pattern");
                
var required = e.getAttribute("required"!= null;
                
                
if(required && !pattern){
                    pattern 
= "\\S";
                    e.setAttribute(
"pattern", pattern);    
                }    
                
                
if(pattern){
                    e.onchange 
= validateOnChange;
                    needsValidation 
= true;
                }
            }
            
if(needsValidation) f.onsubmit = validateOnSubmit;    
        }
    }
    
    
function validateOnChange(){
        
var textfield = this;
        
var pattern = textfield.getAttribute("pattern");
        
var value = this.value;
        
        
if(value.search(pattern) == -1) textfield.className = "invalid";
        
else textfield.className = "valid";    
    }
    
    
function validateOnSubmit(){
        
var invalid = false;
        
for(var i = 0; i < this.elements.length;i++){
            
var e = this.elements[i];
            
if(e.type == "text" && e.onchange == validateOnChange){
                e.onchange();    
                
if(e.className == "invalid") invalid = true;
            }
        }
        
if(invalid){
            alert(    
"The form is incompletely or incorrectly filled out.\n"+
                    
"Please correct the hightlighted fields and try again.");
            
return false;
        }    
    }    
}
)();

 主题的思想是这样,先定义合法和非法时需要做的动作(在这个例子中,只是简单的将input框的背景色改变而已,这个可以通过CSS轻易实现,你也可以弹出警告等来提示用户),然后,当框中的内容改变时,就尝试去匹配这个input定义的模式(如果有的话),如果不匹配,就以一定的方式来提醒用户。

 

整个验证器的思路非常清晰,代码也很简洁,主要是通过自定义规则可以完成任意的验证方式。

posted @ 2008-12-30 21:08 abruzzi 阅读(82) 评论(0) 编辑


作者: abruzzi  链接:http://abruzzi.javaeye.com/blog/292002  发表时间: 2008年12月12日

声明:本文系JavaEye网站发布的原创博客文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

前一向研究HMM(隐马尔可夫模型),在wiki上看到了一个python实现的forward&viterbi算法,就先放在这里,过几天研究。HMM是人们在研究人工智能(语音设别,模式识别)方面的一个重要理论,差不多相当于“知道一些线索的情况下,跟据结论推出结果”的算法,很简单,很强大!

 

好了,先看看这个代码,慢慢分析之

 

def forward_viterbi(obs, states, start_p, trans_p, emit_p):
   T 
= {}
   
for state in states:
       
##          prob.           V. path  V. prob.
       T[state] = (start_p[state], [state], start_p[state])
   
for output in obs:
       U 
= {}
       
for next_state in states:
           total 
= 0
           argmax 
= None
           valmax 
= 0
           
for source_state in states:
               (prob, v_path, v_prob) 
= T[source_state]
               p 
= emit_p[source_state][output] * trans_p[source_state][next_state]
               prob 
*= p
               v_prob 
*= p
               total 
+= prob
               
if v_prob > valmax:
                   argmax 
= v_path + [next_state]
                   valmax 
= v_prob
           U[next_state] 
= (total, argmax, valmax)
       T 
= U
   
## apply sum/max to the final states:
   total = 0
   argmax 
= None
   valmax 
= 0
   
for state in states:
       (prob, v_path, v_prob) 
= T[state]
       total 
+= prob
       
if v_prob > valmax:
           argmax 
= v_path
           valmax 
= v_prob
   
return (total, argmax, valmax)

 

 python就是简单,这要撂别的高级语言,这么短的代码说不定连局部变量还没初始化完呢。这个是算法主题,下面是如何测试这个算法的一个小函数和几个状态集合。

 


states = ('Rainy''Sunny')
 
observations 
= ('walk''shop''clean')
 
start_probability 
= {'Rainy'0.6'Sunny'0.4}
 
transition_probability 
= {
   
'Rainy' : {'Rainy'0.7'Sunny'0.3},
   
'Sunny' : {'Rainy'0.4'Sunny'0.6},
   }
 
emission_probability 
= {
   
'Rainy' : {'walk'0.1'shop'0.4'clean'0.5},
   
'Sunny' : {'walk'0.6'shop'0.3'clean'0.1},
   }
   
#A simple example of the using algorithm
def example():
    
return forward_viterbi(observations,
                           states,
                           start_probability,
                           transition_probability,
                           emission_probability)
print example()

 

okay,这些代码放入一个文件,就可以做一个简单的测试了。先把代码放在这里,过几天完整的分析一下。

 

posted @ 2008-12-30 21:08 abruzzi 阅读(113) 评论(0) 编辑