经过多次认真的讨论研究,我们认为我们的技术难点在于爬虫的多线程技术。

一、使用多线程的原因

  多线程是一种机制,它允许在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间互相独立。多个线程的执行是并发的,即在逻辑上是“同时”的。而我们做的爬虫需要爬取多个网页,如果单一的用单线程,效率十分低下。为了提升爬虫的性能,需要采用多线程的爬虫技术。而且很多大型网站都采用多个服务器镜像的方式提供同样的网页内容。采用多线程并行抓取能同时获取同一个网站的多个服务器中的网页,这样能极大地减少抓取这类网站的时间。

二、难点何在

  由于多线程的线程是并发的,逻辑上控制很难。尤其是同步和死锁的问题。并且对于并行爬虫架构而言,处理空队列要比序列爬虫更加复杂。空的队列并不意味着爬虫已经完成了工作,因为此刻其他的进程或线程可能依然在解析网页,并且马上会加入新的URL。进程或线程管理员需要给报告队列为空的线程发送临死的休眠信号来解决这类问题。线程管理员需要不断跟踪休眠线程的数目;只有当所有的线程都休眠的时候爬虫才可以终止。加上我们的JAVA水平并不高,对于线程的应用很少。所以这无疑成为我们的最大难点。

三、怎么解决

1)同步线程

 

  许多线程在执行中必须考虑与其他线程之间共享数据或协调执行状态。这就需要同步机制。在Java中每个对象都有一把锁与之对应。但Java不提供单独的lock和unlock操作。它由高层的结构隐式实现,来保证操作的对应。(然而,我们注意到Java虚拟机提供单独的monito renter和monitorexit指令来实现lock和unlo ck操作。) synchronized语句计算一个对象引用,试图对该对象完成锁操作,并且在完成锁操作前停止处理。当锁操作完成synchronized语句体得到执行。当语句体执行完毕(无论正常或异常),解锁操作自动完成。作为面向对象的语言,synchronized经常与方法连用。一种比较好的办法是,如果某个变量由一个线程赋值并由别的线程引用或赋值,那么所有对该变量的访问都必须在某个synchromized语句或synchronized方法内。

  现在假设一种情况:线程1与线程2都要访问某个数据区,并且要求线程1的访问先于线程2,则这时仅用synchronized是不能解决问题的。这在Unix或Windows NT中可用Simaphore来实现。而Java并不提供。在Java中提供的是wait()和notify()机制。使用如下:

synchronized method-1(…){ call by thread 1.
  ∥access data area;
  available=true;
  notify() 
  }
  synchronized method-2(…){∥call by thread 2.
  while(!available)
 try{
  wait();∥wait for notify().
  }catch (Interrupted Exception e){
  }
  ∥access data area
  }

   其中available是类成员变量,置初值为false。

  如果在method-2中检查available为假,则调用wait()。wait()的作用是使线程2进入非运行态,并且解锁。在这种情况下,method-1可以被线程1调用。当执行notify()后。线程2由非运行态转变为可运行态。当method-1调用返回后。线程2可重新对该对象加锁,加锁成功后执行wait()返回后的指令。这种机制也能适用于其他更复杂的情况。

 

(2)死锁

  如果程序中有几个竞争资源的并发线程,那么保证均衡是很重要的。系统均衡是指每个线程在执行过程中都能充分访问有限的资源。系统中没有饿死和死锁的线程。Java并不提供对死锁的检测机制。对大多数的Java程序员来说防止死锁是一种较好的选择。最简单的防止死锁的方法是对竞争的资源引入序号,如果一个线程需要几个资源,那么它必须先得到小序号的资源,再申请大序号的资源。