《Java 线程编程》 学习笔记3

《Java 线程编程》 学习笔记3

第3章 创建和启动线程

3.2 线程命名:getName() 和 setName()
3.2.1 使用getName()
  1. 在 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
        ...
        这反映了多线程的『非确定性』
*/
  1. 除了名字为 main 的线程,JavaVM 还会自动启动其他线程。
JDK1.2JDK1.1JDK1.0
mainmainmain
FinalizerFinalizer threadFinalizer thread
Reference Handler
Signal dispatcher
AWT-WindowsAWT-WindowsAWT-Win32
AWT-EventQueue-0AWT-EventQueue-0AWT-Callback-Win32
SunToolkit.PostEventQueue-0
Screen UpdaterScreen UpdaterScreen Updater

- 命名为 main 和 Finalizer 的线程在任何应用程序中都会自动启动。当应用程序包含 AWT 或 Swing 中的任何图形组件时,JavaVM 也会启动余下的线程。因此,具有 GUI 的 JDK1.2 应用程序会自动启动 8个线程。

3.2.2 使用 setName()
  1. 使用 public final void setName(String newName) 方法来给线程命名。
    使用技巧:
    给线程命名时,应遵循以下几点:
    1. 在 start( ) 之前,对 Thread 上调用 setName( ),线程启动后不再对它进行命名
    2. 尽可能给每个线程起一个简短,有意义的名字
    3. 给每个线程起一个不相同的名字
    4. 不要改变 JavaVM 线程的名字
3.3 线程构造函数
  1. Thread 类的中心构造函数:public Thread(ThreadGroup group, Runnable target, String name)
    • group 指明线程所属于的线程组
    • target 指明执行方法
    • name 指明线程名
  2. 线程组与线程关系:
    1. 在 Java 中,ThreadGroup 可以包含 Thread 以及 ThreadGroup。每个 Thread 都是 ThreadGroup 的一个成员,它本身也可能是另一个 ThreadGroup 的成员。
    2. 如果线程构造时期,没有指定 ThreadGroup,则 ThreadGroup 从构造它的 Thread 继承。
      Alt text
3.4 激活线程:start() 和 isAlive()
  1. start() 方法通知线程规划器这个新线程已经准备就绪。public native synchronized void start() throws IllegalThreadStateException 可以看出 start() 方法使用了 JNI,即本地库。
  2. isAlive() 用于测试线程是否仍然存活。public final native boolean isAlive()
3.5 Thread.sleep()
  1. 让线程暂停休眠一会儿。比如主管显示分钟的线程。
/*
    方法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) {
        // 忽略异常
    }
  1. 休眠是使用繁忙循环的一种替代。休眠线程不会占用任何处理器周期,因为它的执行在指定时间被挂起。sleep() 是静态的,它只将当前执行的线程置入休眠。不能让某个线程将另外的线程置入休眠。
  2. try/catch 是必须的,因为在线程休眠时,它可能被另外的线程中断。一个线程可能希望能中断另一个线程,让它知道必须采取某些行动。

第4章 实现 Runnable 接口与扩展 Thread 类

4.1 可视定时器图形组件的实现
  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 吗?
  1. 使用事件来运行线程显然是不可行的,必须使用另一个线程来运行定时器。
4.3 接口 java.lang.Runnable
  1. 不从 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 准确性
  1. 在 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. 随着时间流逝,1分钟后时间偏离 430ms,2分钟后时间偏离 1s,3分钟后偏离约 1.38s…
4.7 提高 SecondCounter 准确性
  1. 虽然 while 循环中语句不是很多,但事实表明,其中的语句将导致循环的运行速度比预想的慢。为了提高准确性,休眠时间应该根据当前的系统时钟时间有所修正。
  2. 在原先的基础上添加如下代码:
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 小结
  1. 扩展 Thread 并非总是首选途径,第二种允许线程运行在类中的方式是实现 Runnable。事实上,在大部分情况下,实现 Runnable 更优于扩展 Thread。
  2. 经验:
    • 不要使用事件处理现货出呢个来执行长运行时间的操作
    • 适当使用关键字 volatile 有时会起很大的作用
    • 即使只有少数几条语句,其执行时间也可能不可预测。如果这些语句在循环中反复执行,执行时间可能变得相当长。当准确性非常重要时,则应当通过系统时钟来检查真正所用的时间,然后进行校正。

第5章 完美终止线程

5.1 中断线程:interrupt()
  1. 当一个线程运行时,另一个线程可以调用对应的 Thread 对象的 interrupt() 方法来中断它:public void interrupt()
  2. 这个方法只是在目标线程中设置一个标志位,表示它已经被中断,并立即返回。该方法可能抛出 SecurityException,表示发出中断请求的线程没有权限中断其他线程
    • 在 Thread 上调用 checkAccess() 方法可以进行安全性检查,这个方法又会调用 SecurityManager 的 checkAccess(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 中断休眠线程
  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");
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值