2008-09-10

Java的线程与高级线程处理

线程:
  分时系统的概念就是计算机系统按时间轮流执行每个进程。在这个过程中保持当前进程的状态并且切换到下一个进程。
  线程拓展了这个概念,把不同进程之间的切换改换为单个进程内部的不同模块之间的切换。
  操作系统在不同进程之间切换的开销一般会比jvm在不同线程之间的开销大,所以,一般来说线程可以使程序更快,此外,多线程还可以使得交互程序不至于被锁定在等待用户动作上。

获得新线程:
  在java中,有两种方法可以创建线程,扩展Thread类或者实现java.lang.Runnable接口,然后在Thread构造函数中使用。实际上Thread类本身已经实现了Runnable接口了。

  对于第一种方法,声明了一个继承了Thread类的对象就可以得到一个新的线程,不过创建并不等于开始执行,线程的执行通过调用Thread.start()方法触发(实际上,继承Thread的类自己没有这个方法,是调用Thread类的这个方法),而线程执行的内容是从run()开始的。基本上使用时常常把声明和启动线程一起写了:
new ThreadExtendObject().start();

  对于第二种方法,Runnable接口的定义如下:
public interfae Runnable{
  public void run();
}

所以实现的类必须实现run方法。而运行的时候则通过把这个实现了Runnable接口的类的对象传递给Thread构造函数来实现。
例如:
class Testtt implements Runnable{
  public void run() {}
}

Thread test1 = new Thread(new Testtt());

然后在调用这个test1的Thread方法,来处理这个线程。通常上面最后一句也习惯同时开始执行,因此这样写:
new Thread(new Testtt()).start();

  此外还有需要注意的一点是,Thread类的方法只有在Thread类对象存在的时候才能调用,为了得到Thread对象,必须调用Thread.currentThread()方法,它返回当前正在运行的线程,

线程的优先级:
  java中可以改变线程的优先级,使得线程可以调整执行顺序,java线程可以采用时间片机制,也可以不采用,因此,正在运行的线程可以选择是否与同优先级的线程共享处理器。
  java的优先权分配是1(最低),10(最高)。线程一开始的时候和它的父线程有相同的优先级,
  如果不采用java时间片分配,由于不能确保时间片的分配,因此,一旦线程开始执行,其他所有具有相同优先权的线程可能会被锁死,因此最好让线程频繁的让出对cpu的控制,一边其他线程执行,比如让比较占用cpu时间的线程按一定的时间间隔调用yield()方法,
  如果采用java标准的时间片分配则不必担心这个。
  在java中,设定线程的优先级使用:
threadobj.setPriority(int newPriority);

线程的编程:
  要协调线程需要用到同步技术,不同线程需要不同的同步方式,大致分为四类,且难度随序号上升:

  1. 不相关的线程
  2. 相关但不需要同步的线程
  3. 互斥线程
  4. 交互式互斥线程
第一种最简单,只是纯粹的调用类对象来运行线程。最典型的例子是两个不同的类的类对象。
第二种类型的线程处理,一般只需要注意不要让两个不同的线程作用于同一个数据。另外还有一个例子和这种类型类似,就是daemon线程,也就是守护线程,它在后台运行,为其他程序或者线程提供服务,可以使用Threadobj.setDemon(true);来把一个线程设定为守护线程。

第三种和第四种是较高级的线程,于前两种比起来,也复杂得多。

互斥线程:
  一个简单的互斥线程的应用:就是当各个线程都会读取同一个数据并且修改它的时候,如果不使用互斥线程,这种情况下,会产生所谓的“数据竞赛”,最后造成数据的错误。因此,我们需要做一些处理以避免数据竞赛的产生:
  • 无论两个线程怎样访问同一个数据,都用互斥的方法处理
  • 读写操作不能同时访问同一数据
  Java中,线程的互斥是在对象数据的基础之上的。系统中的每个对象都有自己的信号标识,仅当用到时它才能分配,它用于同步。使用synchronized关键字来标记某一个Object对象,runtime将会控制在一个给定时刻最多只有一个线程能够锁住标记的Object对象,在这个对象的所有同步方法中一次只有一个线程能够运行。
  synchronized可以用于一个类的所有方法或者一个方法,或者一个代码块。必须得到某个指定对象的互斥锁,然后代码才能执行,执行之后才会释放锁,如果锁被其他线程占用,那么申请使用锁的线程就会等待锁的释放。
  比如,需要把整个类指定为互斥的,就把synchronized应用到static方法中。需要把代码块定义为互斥的,只需要把synchronized加到代码块的前面,不过必须指定一个用于控制的对象(因为前面提到了,java的线程互斥是在对象的基础上的),这个对象将被用来进行同步。
  最后,当把单个方法定义为互斥的时候,直接在方法前面加synchronized就可以了。之所以能这样是因为这个时候互斥锁隐式使用了this对象。
  最后的最后,这个东西一定要谨记的是,同步互斥的线程只能使用同一个对象,所以当有n个线程调用同一个对象的方法时,方法的同步是有用的,这样在任意时间内,同一个对象的所有需要同步的方法最多只能有一个方法被调用。

第四种交互式的互斥线程是最复杂的。也是最困难的。因此也就是所谓的高级线程处理了。
典型的情况是,当一个线程等待其他线程提供数据,但数据尚未就绪时,它将因等待数据而暂停执行,就是所谓的wait/notify线程同步问题。这种机制是一种复杂的独立于语言的协议。所以,在有需要的时候我再来学习它,不过我还是了解了一下它的原理:

这种情况下有两个队列,一个是被封锁的队列,等待获得互斥锁后才开始运行,另一个是等待队列,当当前线程发现它需要的数据尚未就绪的和时候它就调用wait()方法进入等待队列。
当一个线程进入等待队列后,封锁队列中的线程可以开始运行。之后,线程会产生数据,然后调用notify()方法,如果这些数据是等待队列中的线程所需的,它将唤醒等待队列中,否则把等待队列中的线程移动到封锁队列中。这是,调用notify()结束,一轮循环完成

没有评论: