《Java大学教程》—第22章 多线程程序

22.2 进程(process):P551
时间切片(time-slicing):处理器只是完成了一个任务的一部分工作,然后完成下一个任务的一部分工作,
因为处理吕每次完成工作的时间都非常短,因此看起来这些任务是同时完成的。

进程:一个运行的程序通常称为一个进程。
并发进程(concurrent process):两个或多个可以同时执行的进程称为并发进程。
在多个进程并发执行时,每个进程都有自己的存放程序代码和数据的存储空间,并且每个进程的存储空间对于其他进程都是受保护的,所有这些工作都由操作系统完成。

22.3    线程(thread):P552
线程:一个程序执行的多个独立任务称为线程。通常将线程称为轻量级进程(lightweight process),因为也管理进程相比,管理线程需要占用较少的系统资源。
线程没有完全独立的存储空间,线程之间共享代码区和存储区。
管理线程的方式也是基于时间切片原则,由JVM和OS协同管理。

22.4    Thread类
继承Thread类实现线程。控制线程用三个方法:start(), run(), finish()
主要执行代码在run()方法中。
注:run()方法结束后,这个线程就运行结束了,不可以再次调用,必须重新创建。原因也可参考图22-2的状态转换图,线程进入TERMINATE状态中,无法再次启动。

22.5    线程的执行与调度
份额(quantum):每个进程或者线程都可以获得处理器上的一小段时间--称为份额,然后轮转到下一个进程或线程。
份额内时间没有用完时,也可以使用sleep()方法,强制放弃CPU的占用权,将CPU还给OS,从而可以继续分配给下一个进程或线程。
注:sleep()方法传入的时间间隔(以毫秒为单位),可能会抛出InterruptedException异常,必须写在try...catch代码块中。

22.6    Runnable接口
创建一个实现Runnable接口的类,然后将类的实例作为Runnable接口的参数传入Thread对象的构造函数中。
后面的工作主要就是操作Thread的对象,与前面扩展Thread类的方法一样了。
注:此处的Thread对象是独立创建的,前面是对Thread类的继承。

22.7    线程同步:P562
异步行为(asynchronous behaviour):
    在一般情况下两个或多个并发执行线程的行为并不是协同的,而且无法预知在某个时间段内哪个线程将会占用CPU,这种非协同的行为称为异步行为。
互斥访问(mutual exclusion):需要将访问缓冲区的程序段看作临界区(critical section)--即在任何时刻只能被一个进程访问的区域,实现临界区的方式称为互斥访问。
忙等待(busy waiting):被某个线程执行的方法循环中反复执行,直到满足某个条件才能终止。效率太低,可以使用wait()方法将线程挂起,直到被其他线程的消息唤醒。
在Java中创建一个monitor对象(监视器),monitor类的所有方法在任何时刻都只能被一个线程访问。
可以在方法的头部使用synchronized修饰符,由于方法被同步(synchronized),所以一旦有某个对象调用该方法,那么该方法将被置上一个锁(lock),
其他对象必须在该方法执行完成后才能访问它。
注1:为什么要加锁?是因为你有临界区,对这个临界区加锁才是目的。
因此锁的目的:当前类(即this.静态变量),当前对象(即this.变量),当前对象中创建的对象(即this.对象变量),外部传入到当前对象中的对象。

注2:什么可以加锁?对象,只有对象可以加锁。
在Java中,每一个对象都拥有一个锁标记(monitor),也称为监视器,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问。
JVM会记录并且监管这个锁,当线程获取锁后计数器由0变1,线程若访问同样锁的其他方法,计数器还会继续增加,从而保证线程完全释放这个锁后才会允许其他线程访问。
所以基本数据类型的变量是不能作为临界区的,也就不能被加锁。

注3:怎么样加锁?使用synchronized对静态方法、方法、对象变量加锁。
static synchronized aStaticMethod(){...}        //        “当前类(即this.静态变量)”加锁
synchronized aMethod(){...}        //        “当前对象(即this.变量)”加锁
synchronized (aObjectVariable){...}        //        “当前对象中创建的对象(即this.对象变量)”或“外部传入到当前对象中的对象”加锁。

22.8    线程状态:P563
状态转换图:图22-2 一个线程的状态转换图
start()启动一个线程-->线程进入ready(就绪)状态。
dispatch()调度一个线程-->线程进入running(运行)状态。
线程运行时间超时、yield()强制一个线程放弃CPU-->线程进入ready(就绪)状态。
sleep()睡眠一个线程-->线程进入sleeping(睡眠)状态-->睡眠时间超时-->线程进入ready(就绪)状态。
wait()-->线程进入waiting(等待)状态-->获得其他线程notify()或notifyAll()方法通知-->线程进入ready(就绪)状态。
由于等待输入或等待外部设备可用-->线程处于blocked(阻塞)状态-->阻塞时间超时或设备正常-->线程进入ready(就绪)状态。
run()方法完成后,线程被终止。

22.10    Timer类:调度线程
在固定时间间隔内生成ActionEvents对象。因此,必须将一个Timer对象关联到一个ActionListener对象。
只要Timer对象生成一个ActionEvent对象,都会执行与Timer对象相关的ActionListener对象的actionPerformed()方法。

自测题:
1.    如何通过时间切片实现并发:P552
时间切片(time-slicing):处理器只是完成了一个任务的一部分工作,然后完成下一个任务的一部分工作,
因为处理吕每次完成工作的时间都非常短,因此看起来这些任务是并发完成的。

2.    进程与线程的区别:P552
每个进程都有完全独立的存储空间,用于存放程序的代码和数据,并且每个进程的存储空间对于其他进程都是受保护的,所有这些工作都由操作系统完成。
每个线程没有完全独立的存储空间,线程之间共享程序的代码和数据,线程的调度由JVM和操作系统协同完成。

3.    异步线程执行和同步线程执行的区别:P562
异步执行线程:无法预知某个时间段内哪个线程将会占用CPU,也无法估计程序执行的结果。
同步执行线程:确保线程对于临界区是互斥访问的,当某个线程执行同步代码时,其他线程无法再调用同步代码,必须等待前面执行的进程解锁。

4.    临界区和互斥的概念:P562
访问缓冲区的程序段看作临界区(critical section)--即在任何时刻只能被一个进程访问的区域。
实现临界区的方式称为互斥访问(mutual exclusion)。
Jav提供了在多线程程序中实现互斥的机制,即每个对象都有一个monitor(监视器),monitor类的所有方法在任何时刻都只能被一个线程访问。
只需要在方法的头部使用synchronized修饰符,由于方法被同步,所以一旦有某个对象调用该方法,那么这个方法就会被上锁,其他对象如果同时想访问就必须等待。

5.    如何在Java程序中避免忙等待:P562
使用wait()方法将线程的执行挂起,直到它收到另一个进程的消息将它唤醒。就可以避免出现忙等待。

6.    Threads6.java    RunThreads6.java

编程练习:代码附件
3.    CounterVersionFour.java    RunCounterVersion.java    CounterThread.java

上一篇:SAP Web Dynpro-门户集成


下一篇:【开发者portal在线开发插件系列一】profile和基本上下行消息