分享
 
 
 

反应灵敏的用户界面

王朝other·作者佚名  2008-05-19
窄屏简体版  字體: |||超大  

反应灵敏的用户界面

作为我们的起点,请思考一个需要执行某些CPU密集型计算的程序。由于CPU“全心全意”为那些计算服务,所以对用户的输入十分迟钝,几乎没有什么反应。在这里,我们用一个合成的applet/application(程序片/应用程序)来简单显示出一个计数器的结果:

//: Counter1.java

// A non-responsive user interface

package c14;

import java.awt.*;

import java.awt.event.*;

import java.applet.*;

public class Counter1 extends Applet {

private int count = 0;

private Button

onOff = new Button("Toggle"),

start = new Button("Start");

private TextField t = new TextField(10);

private boolean runFlag = true;

public void init() {

add(t);

start.addActionListener(new StartL());

add(start);

onOff.addActionListener(new OnOffL());

add(onOff);

}

public void go() {

while (true) {

try {

Thread.currentThread().sleep(100);

} catch (InterruptedException e){}

if(runFlag)

t.setText(Integer.toString(count++));

}

}

class StartL implements ActionListener {

public void actionPerformed(ActionEvent e) {

go();

}

}

class OnOffL implements ActionListener {

public void actionPerformed(ActionEvent e) {

runFlag = !runFlag;

}

}

public static void main(String[] args) {

Counter1 applet = new Counter1();

Frame aFrame = new Frame("Counter1");

aFrame.addWindowListener(

new WindowAdapter() {

public void windowClosing(WindowEvent e) {

System.exit(0);

}

});

aFrame.add(applet, BorderLayout.CENTER);

aFrame.setSize(300,200);

applet.init();

applet.start();

aFrame.setVisible(true);

}

} //

在这个程序中,AWT和程序片代码都应是大家熟悉的,第13章对此已有很详细的交待。go()方法正是程序全心全意服务的对待:将当前的count(计数)值置入TextField(文本字段)t,然后使count增值。

go()内的部分无限循环是调用sleep()。sleep()必须同一个Thread(线程)对象关联到一起,而且似乎每个应用程序都有部分线程同它关联(事实上,Java本身就是建立在线程基础上的,肯定有一些线程会伴随我们写的应用一起运行)。所以无论我们是否明确使用了线程,都可利用Thread.currentThread()产生由程序使用的当前线程,然后为那个线程调用sleep()。注意,Thread.currentThread()是Thread类的一个静态方法。

注意sleep()可能“掷”出一个InterruptException(中断违例)――尽管产生这样的违例被认为是中止线程的一种“恶意”手段,而且应该尽可能地杜绝这一做法。再次提醒大家,违例是为异常情况而产生的,而不是为了正常的控制流。在这里包含了对一个“睡眠”线程的中断,以支持未来的一种语言特性。

一旦按下start按钮,就会调用go()。研究一下go(),你可能会很自然地(就象我一样)认为它该支持多线程,因为它会进入“睡眠”状态。也就是说,尽管方法本身“睡着”了,CPU仍然应该忙于监视其他按钮“按下”事件。但有一个问题,那就是go()是永远不会返回的,因为它被设计成一个无限循环。这意味着actionPerformed()根本不会返回。由于在第一个按键以后便陷入actionPerformed()中,所以程序不能再对其他任何事件进行控制(如果想出来,必须以某种方式“杀死”进程――最简便的方式就是在控制台窗口按Ctrl+C键)。

这里最基本的问题是go()需要继续执行自己的操作,而与此同时,它也需要返回,以便actionPerformed()能够完成,而且用户界面也能继续响应用户的操作。但对象go()这样的传统方法来说,它却不能在继续的同时将控制权返回给程序的其他部分。这听起来似乎是一件不可能做到的事情,就象CPU必须同时位于两个地方一样,但线程可以解决一切。“线程模型”(以及Java中的编程支持)是一种程序编写规范,可在单独一个程序里实现几个操作的同时进行。根据这一机制,CPU可为每个线程都分配自己的一部分时间。每个线程都“感觉”自己好象拥有整个CPU,但CPU的计算时间实际却是在所有线程间分摊的。

线程机制多少降低了一些计算效率,但无论程序的设计,资源的均衡,还是用户操作的方便性,都从中获得了巨大的利益。综合考虑,这一机制是非常有价值的。当然,如果本来就安装了多块CPU,那么操作系统能够自行决定为不同的CPU分配哪些线程,程序的总体运行速度也会变得更快(所有这些都要求操作系统以及应用程序的支持)。多线程和多任务是充分发挥多处理机系统能力的一种最有效的方式。

1 从线程继承

为创建一个线程,最简单的方法就是从Thread类继承。这个类包含了创建和运行线程所需的一切东西。Thread最重要的方法是run()。但为了使用run(),必须对其进行过载或者覆盖,使其能充分按自己的吩咐行事。因此,run()属于那些会与程序中的其他线程“并发”或“同时”执行的代码。

下面这个例子可创建任意数量的线程,并通过为每个线程分配一个独一无二的编号(由一个静态变量产生),从而对不同的线程进行跟踪。Thread的run()方法在这里得到了覆盖,每通过一次循环,计数就减1――计数为0时则完成循环(此时一旦返回run(),线程就中止运行)。

//: SimpleThread.java

// Very simple Threading example

public class SimpleThread extends Thread {

private int countDown = 5;

private int threadNumber;

private static int threadCount = 0;

public SimpleThread() {

threadNumber = ++threadCount;

System.out.println("Making " + threadNumber);

}

public void run() {

while(true) {

System.out.println("Thread " +

threadNumber + "(" + countDown + ")");

if(--countDown == 0) return;

}

}

public static void main(String[] args) {

for(int i = 0; i

new SimpleThread().start();

System.out.println("All Threads Started");

}

}

run()方法几乎肯定含有某种形式的循环――它们会一直持续到线程不再需要为止。因此,我们必须规定特定的条件,以便中断并退出这个循环(或者在上述的例子中,简单地从run()返回即可)。run()通常采用一种无限循环的形式。也就是说,通过阻止外部发出对线程的stop()或者destroy()调用,它会永远运行下去(直到程序完成)。

在main()中,可看到创建并运行了大量线程。Thread包含了一个特殊的方法,叫作start(),它的作用是对线程进行特殊的初始化,然后调用run()。所以整个步骤包括:调用构建器来构建对象,然后用start()配置线程,再调用run()。如果不调用start()――如果适当的话,可在构建器那样做――线程便永远不会启动。

下面是该程序某一次运行的输出(注意每次运行都会不同):

Making 1

Making 2

Making 3

Making 4

Making 5

Thread 1(5)

Thread 1(4)

Thread 1(3)

Thread 1(2)

Thread 2(5)

Thread 2(4)

Thread 2(3)

Thread 2(2)

Thread 2(1)

Thread 1(1)

All Threads Started

Thread 3(5)

Thread 4(5)

Thread 4(4)

Thread 4(3)

Thread 4(2)

Thread 4(1)

Thread 5(5)

Thread 5(4)

Thread 5(3)

Thread 5(2)

Thread 5(1)

Thread 3(4)

Thread 3(3)

Thread 3(2)

Thread 3(1)

可注意到这个例子中到处都调用了sleep(),然而输出结果指出每个线程都获得了属于自己的那一部分CPU执行时间。从中可以看出,尽管sleep()依赖一个线程的存在来执行,但却与允许或禁止线程无关。它只不过是另一个不同的方法而已。

亦可看出线程并不是按它们创建时的顺序运行的。事实上,CPU处理一个现有线程集的顺序是不确定的――除非我们亲自介入,并用Thread的setPriority()方法调整它们的优先级。

main()创建Thread对象时,它并未捕获任何一个对象的句柄。普通对象对于垃圾收集来说是一种“公平竞赛”,但线程却并非如此。每个线程都会“注册”自己,所以某处实际存在着对它的一个引用。这样一来,垃圾收集器便只好对它“瞠目以对”了。

2 针对用户界面的多线程

现在,我们也许能用一个线程解决在Counter1.java中出现的问题。采用的一个技巧便是在一个线程的run()方法中放置“子任务”――亦即位于go()内的循环。一旦用户按下Start按钮,线程就会启动,但马上结束线程的创建。这样一来,尽管线程仍在运行,但程序的主要工作却能得以继续(等候并响应用户界面的事件)。下面是具体的代码:

//: Counter2.java

// A responsive user interface with threads

import java.awt.*;

import java.awt.event.*;

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有