《Java 线程编程》 学习笔记3
第3章 创建和启动线程
3.2 线程命名:getName() 和 setName()
3.2.1 使用getName()
- 在 Thread API 中,使用
public final String getName()
该方法来获取线程的名称。可以用于区分两个线程。
public class TwoThreadGetName extends Thread {
public void run() {
for(int i = 0; i < 10; i++) {
printMsg();
}
}
public void printMsg() {
Thread t = Thread.currentThread();
String name = t.getName();
System.out.println("name = " + name);
}
public static void main(String[] args) {
TwoThreadGetName tt = new TwoThreadGetName();
tt.start();
for(int i = 0; i < 10; i++) {
tt.printMsg();
}
}
}
/*
输出结果:
name = main
name = main
name = main
name = Thread-0
name = main
name = Thread-0
name = main
name = Thread-0
...
这反映了多线程的『非确定性』
*/
- 除了名字为 main 的线程,JavaVM 还会自动启动其他线程。
JDK1.2 | JDK1.1 | JDK1.0 |
---|---|---|
main | main | main |
Finalizer | Finalizer thread | Finalizer thread |
Reference Handler | 无 | 无 |
Signal dispatcher | 无 | 无 |
AWT-Windows | AWT-Windows | AWT-Win32 |
AWT-EventQueue-0 | AWT-EventQueue-0 | AWT-Callback-Win32 |
SunToolkit.PostEventQueue-0 | 无 | 无 |
Screen Updater | Screen Updater | Screen Updater |
- 命名为 main 和 Finalizer 的线程在任何应用程序中都会自动启动。当应用程序包含 AWT 或 Swing 中的任何图形组件时,JavaVM 也会启动余下的线程。因此,具有 GUI 的 JDK1.2 应用程序会自动启动 8个线程。
3.2.2 使用 setName()
- 使用
public final void setName(String newName)
方法来给线程命名。
使用技巧:
给线程命名时,应遵循以下几点:
1. 在 start( ) 之前,对 Thread 上调用 setName( ),线程启动后不再对它进行命名
2. 尽可能给每个线程起一个简短,有意义的名字
3. 给每个线程起一个不相同的名字
4. 不要改变 JavaVM 线程的名字
3.3 线程构造函数
- Thread 类的中心构造函数:
public Thread(ThreadGroup group, Runnable target, String name)
- group 指明线程所属于的线程组
- target 指明执行方法
- name 指明线程名
- 线程组与线程关系:
- 在 Java 中,ThreadGroup 可以包含 Thread 以及 ThreadGroup。每个 Thread 都是 ThreadGroup 的一个成员,它本身也可能是另一个 ThreadGroup 的成员。
- 如果线程构造时期,没有指定 ThreadGroup,则 ThreadGroup 从构造它的 Thread 继承。
3.4 激活线程:start() 和 isAlive()
- start() 方法通知线程规划器这个新线程已经准备就绪。
public native synchronized void start() throws IllegalThreadStateException
可以看出 start() 方法使用了 JNI,即本地库。 - isAlive() 用于测试线程是否仍然存活。
public final native boolean isAlive()
3.5 Thread.sleep()
- 让线程暂停休眠一会儿。比如主管显示分钟的线程。
/*
方法1: 占用大量 CPU 时间
*/
long startTime = System.currentTimeMillis();
long stopTime = startTime + 60000;
while(System.currentMillis() < stopTime) {
// 不做任何事,只回送
}
/*
方法2: 线程休眠
*/
public static native void sleep(long msToSleep) throws InterruptedException;
try {
Thread.sleep(60000);
}
catch(InterruptedException x) {
// 忽略异常
}
- 休眠是使用繁忙循环的一种替代。休眠线程不会占用任何处理器周期,因为它的执行在指定时间被挂起。sleep() 是静态的,它只将当前执行的线程置入休眠。不能让某个线程将另外的线程置入休眠。
- try/catch 是必须的,因为在线程休眠时,它可能被另外的线程中断。一个线程可能希望能中断另一个线程,让它知道必须采取某些行动。
第4章 实现 Runnable 接口与扩展 Thread 类
4.1 可视定时器图形组件的实现
- SecondCounterLockUp 实现定时器
- 结果:视图卡死
- 原因:AWT-EventQueue-0 线程既用于绘制,又用于调用该事件处理方法。由于 AWT-EventQueue-0 线程一直处于繁忙,所以,永远没有机会再次调用 paint() 方法,因此,显示一直不变。应用程序总是被锁定的,陷入 while 循环之中。
技巧:
GUI 事件处理代码应该相对简短,让事件处理线程从处理器返回,并准备处理下一个事件。如果必须执行更长的任务,应该将工作量传给另一个线程处理。这有助于保持用户界面处于激活状态并具有高灵敏度。
/*
SecondCounterLockUp 实现定时器的第一次尝试
*/
public class SecondCounterLockUp extends JComponent {
private boolean keepRunning;
private Font paintFont;
private String timeMsg;
private int arcLen;
public SecondCounterLockUp() {
paintFont = new Font("SansSerif", Font.BOLD, 14);
timeMsg = "never started";
arLen = 0;
}
public void runClock() {
System.out.println("thread running runClock() is " + Thread.currentThread().getName());
DecimalFormat fmt = new DecimalFormat("0.000");
long normalSleepTime = 100;
int counter = 0;
keepRunning = true;
while(keepRunning) {
try {
Thread.sleep(normalSleepTime);
}
catch (InterruptedException x) {
// 略
}
counter++;
double counterSecs = counter / 10.0;
timeMsg = fmt.format(counterSecs);
acrLen = (((int)counterSecs) % 60) * 360 / 60;
repaint();
}
}
public void stopClock() {
keepRunning = false;
}
public void paint(Graphics g) {
System.out.println("thread that invokes paint() is " + Thread.currentThread().getName());
g.setColor(Color.black);
...
}
}
// 调用的 Frame
public class SecondCounterLickupMain extends JPanel {
private SecondCounterLockup sc;
private JButton startButton;
private JButton stopButton;
public SecondCounterLockupMain() {
sc = new SecondCounterLockup();
startButton = new JButton("start");
stopButton = new JButton("stop");
stopButton.setEnabled(false);
startButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
startButton.setEnabled(false);
sc.runClock();
stopButton.setEnabled(true);
stopButton.requestFocus();
}
});
stopButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
stopButton.setEnabled(false);
sc.stopClock();
startButton.setEnabled(true);
startButton.requestFocus();
}
});
}
// 主函数,入口
public static void main(String[] args) {
...
}
}
4.2 能扩展 Thread 和 JComponent 吗?
- 使用事件来运行线程显然是不可行的,必须使用另一个线程来运行定时器。
4.3 接口 java.lang.Runnable
- 不从 Thread 继承,类可以实现接口 java.lang.Runnable,让线程在其中运行。
4.4 把 Runnable 对象传递给 Thread 的构造函数
技巧:
实现 Runnable 接口代替扩展 Thread 通常是一种更佳的选择,即使该类只从 Object 继承。这能让你开发和使用实现 Runnable 接口的类的一般技术,而不用关心这个 Runnable 扩展是哪一个具体的类。
4.5 修改 SecondCounter 来使用 Runnable
public class SecondCounterRunnable extends JComponent implements Runnable {
...
public void run() {
runClock();
}
...
}
4.6 检查 SecondCounter 准确性
- 在 SecondCounterRunnable 中 while 循环的每次迭代期间,随着计数器的增加,每 100ms 的休眠获得 1/10 秒延迟。用于执行循环中其他语句的时间有多长呢?这会导致定时器不准确吗?也许会,但误差会相当大吗?添加时间跟踪代码如下:
// 在 runClock() 函数内
...
long startTime = System.currentTimeMillis();
...
while(keepRunning) {
...
double elapsedSecs = (System.currentTimeMillis() - startTime) / 1000.0;
double diffSecs = counterSecs - elapsedSecs;
timeMsg = fmt.format(counterSecs) + " - " + fmt.format(elapsedSecs) + " = " + fmt.format(diffSecs);
...
}
...
- 随着时间流逝,1分钟后时间偏离 430ms,2分钟后时间偏离 1s,3分钟后偏离约 1.38s…
4.7 提高 SecondCounter 准确性
- 虽然 while 循环中语句不是很多,但事实表明,其中的语句将导致循环的运行速度比预想的慢。为了提高准确性,休眠时间应该根据当前的系统时钟时间有所修正。
- 在原先的基础上添加如下代码:
public class SecondCounter {
...
long normalSleepTime = 100;
long nextSleepTime = normalSleepTime;
...
public void runClock() {
...
while(keepRunning) {
...
// 利用反馈的思想!!!使用系统时钟校准自己!!!
nextSleepTime = normalSleepTime + (long)(diffSecs * 1000.0);
if (nextSleepTime < 0)
nextSleepTime = 0;
...
}
}
...
}
4.8 小结
- 扩展 Thread 并非总是首选途径,第二种允许线程运行在类中的方式是实现 Runnable。事实上,在大部分情况下,实现 Runnable 更优于扩展 Thread。
- 经验:
- 不要使用事件处理现货出呢个来执行长运行时间的操作
- 适当使用关键字 volatile 有时会起很大的作用
- 即使只有少数几条语句,其执行时间也可能不可预测。如果这些语句在循环中反复执行,执行时间可能变得相当长。当准确性非常重要时,则应当通过系统时钟来检查真正所用的时间,然后进行校正。
第5章 完美终止线程
5.1 中断线程:interrupt()
- 当一个线程运行时,另一个线程可以调用对应的 Thread 对象的 interrupt() 方法来中断它:
public void interrupt()
- 这个方法只是在目标线程中设置一个标志位,表示它已经被中断,并立即返回。该方法可能抛出 SecurityException,表示发出中断请求的线程没有权限中断其他线程。
- 在 Thread 上调用
checkAccess()
方法可以进行安全性检查,这个方法又会调用 SecurityManager 的checkAccess(Thread)
方法。
- 在 Thread 上调用
警告:
SecurityException 属于 RuntimeException 的一个子类,因此,对于可能抛出此异常的 Thread 或 ThreadGroup 的任何方法,并不需要 try/catch 块。默认时,应用程序没有定义 SecurityManager。所以,在代码中需要进行一般性检查,使用方法System.getSecurityManager()
。如果返回 null,则表示没有安装 SecurityManager。如果没有返回 null,则调用 SecurityManager 时必须小心。常见覆写 SecurityManager 方法:
import java.io.*;
class PasswordSecurityManager extends SecurityManager {
private String password;
PasswordSecurityManager(String password) {
super();
this.password = password;
}
private boolean accessOK() {
int c;
//DataInputStream dis = new DataInputStream(System.in);
BufferedReader dis = new BufferedReader(new InputStreamReader(System.in));
String response;
System.out.println("What's the secret password?");
try {
response = dis.readLine();
if (response.equals(password))
return true;
else
return false;
} catch (IOException e) {
return false;
}
}
public void checkRead(FileDescriptor filedescriptor) {
if (!accessOK())
throw new SecurityException("Not a Chance!");
}
public void checkRead(String filename) {
if (!accessOK())
throw new SecurityException("No Way!");
}
public void checkRead(String filename, Object executionContext) {
if (!accessOK())
throw new SecurityException("Forget It!");
}
public void checkWrite(FileDescriptor filedescriptor) {
if (!accessOK())
throw new SecurityException("Not!");
}
public void checkWrite(String filename) {
if (!accessOK())
throw new SecurityException("Not Even!");
}
}
5.1.1 中断休眠线程
- 代码示例:
public class SleepInterrupt extends Object implements Runnable {
public void run() {
try {
System.out.println("in run - about to sleep for 20 seconds");
Thread.sleep(20000);
System.out.println("in run() - woke up");
} catch(InterruptionException x) {
System.out.println("in run() - interrupted while sleeping");
return;
}
System.out.println("in run() - doing stuff after nap");
System.out.println("in run() - leaving normally");
}
public static void main(String[] args) {
SleepInterrupt si = new SleepInterrupt();
Thread t = new Thread(si);
t.start();
// 确保新线程有机会运行一段时间
try {
Thread.sleep(2000);
}
catch(InterruptedExcepted x) {
}
System.out.println("in main() - interrupting other thread");
t.interrupt();
System.out.println("in main() - leaving");
}
}