`
623deyingxiong
  • 浏览: 190147 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

线程对象(Concurrency Tutorial 2)

阅读更多
线程对象

每个线程都关联一个Thread类实例。创建一个并发应用有两种基本策略:
  • 直接控制线程创建和管理,每当你的应用需要一个异步运行的任务时简单地实例化一个Tread类,并启动它。
  • 创建线程,然后托管你的线程管理,把应用程序的任务执行及管理交给executor。

这一节里我们讨论Thread对象。Executors将在高级并发对象(high-level concurrency objects)中讨论。

定义和启动一个线程

一个应用要创建一个线程实例必须提供在线程中要run的代码。有两种方式:
  • 提供一个Runnable对象。Runnable接口定义了一个方法run,run方法中包含了在线程中运行的代码。Runnable对象被传递给Thread的构造器,举例说明如下:
public class HelloRunnable implements Runnable {

    public void run() {
        System.out.println("Hello from a thread!");
    }

    public static void main(String args[]) {
        (new Thread(new HelloRunnable())).start();
    }

	}

  • 继承Thread。Thread类本身实现了Runnable接口,尽管它的run实现是空的(没有一行代码)。继承了Thread的子类通过重写run方法,提供自己的run实现。举例说明如下:
public class HelloThread extends Thread {

    public void run() {
        System.out.println("Hello from a thread!");
    }

    public static void main(String args[]) {
        (new HelloThread()).start();
    }

}



注意,两个例子都通过调用Thread.start来启动一个新线程。

这两种风格你该用哪一种呢?第一种采用(employs)了Runnable对象,这种风格更普适(general),因为Runnable对象在实现Runnable接口的同时可以继承其他父类,而第二种不可以再继承Thread以外的父类。第二种风格在简单的应用中更易于使用,但是你的线程任务类只能是Thread的子类。本课程只关注第一种方法(approach),将Runnable任务的实现和执行Runnable任务的Thread对象分开。不只是因为这种方法更复杂(flexible),更重要的是,这种方法适用于后面要讲到的高级线程管理编程接口(APIs)。

Thead类定义了很多在线程管理中很有用的方法,其中的有些静态方法会返回线程的信息(譬如Thread.currentThread()),还有些静态方法的调用将影响当前线程的状态(譬如 thread.yield())。其他的方法(成员方法)由管理线程和线程对象的线程调用(有点拗口,意思是调用方法影响的是该方法所属线程对象所代表的线程,经常用于线程管理,例如thread.interrupt(),一般情况下线程不会interrupt自己)。我们会在接下来的章节中测试(examine)这些方法。

Sleep暂停线程执行

Thread.sleep 使当前线程暂停执行指定时长。此方法可以很有效地把处理器时间转让给同一进程中的其他线程或同一系统中的其他进程。Sleep方法也用来控制执行进度,就像下面的例子中展示的那样,还可以用来等待另一个有执行时间需求的线程,就像后面的章节中的SimpleThreads例子展示的那样。

有两个重载的sleep版本可供使用:一个睡眠时长的单位是毫秒(millisecond),另一个睡眠时长的单位是纳秒(nanosecond)。然而,由于底层操作系统的限制,这两个方法都不保证睡眠时长是精确的。在后面的章节中我们还会看到,睡眠会因被中断(interrupts)而终结。无论如何,你都不能假设调用sleep会精确地暂停当前线程指定时长。

SleepMessages例子使用sleep每隔四秒打印一条消息:
public class SleepMessages {
    public static void main(String args[])
        throws InterruptedException {
        String importantInfo[] = {
            "Mares eat oats",
            "Does eat oats",
            "Little lambs eat ivy",
            "A kid will eat ivy too"
        };

        for (int i = 0;
             i < importantInfo.length;
             i++) {
            //Pause for 4 seconds
            Thread.sleep(4000);
            //Print a message
            System.out.println(importantInfo[i]);
        }
    }
}


注意到main方法声明抛出了InterruptedException异常,这实际上是sleep方法在当前线程睡眠过程中被另一个线程中断时抛出的异常。因为程序并没有定义可能会中断当前线程的另一个线程,所以就不用多此一举捕获异常了。

中断(Interrupts)

一个interrupt是一个通知,告诉一个线程“你应该停止正在做的事情,做点别的!”(类似下课铃声,告诉你你需要放松放松了。你应该去上厕所,当然你也可以选择继续学习)。程序员可以任意决定一个线程究竟如何响应一次中断,但是一般情况下你都应该选择终止你的线程。这就是本节中要着重强调的做法(usage)。

一个线程通过调用另一线程对应线程对象的interrupt方法来向该线程发送一次中断通知。为了使中断机制(the interrupt mechanism)能够正常工作,这个被中断的线程必须支持自己的中断。

支持中断

一个线程该如何支持自己的中断?这取决于它正在做什么。如果一个线程频繁地调用抛出InterruptedException异常的方法,只要在捕获异常时选择退出run方法就好了。例如在SleepMessage例子中,假设消息循环在Runnable对象的run方法中,为了支持中断可以这样做:
for (int i = 0; i < importantInfo.length; i++) {
    // Pause for 4 seconds
    try {
        Thread.sleep(4000);
    } catch (InterruptedException e) {
        // 我们被中断了,不要再打印消息了。
        return;
    }
    // Print a message
    System.out.println(importantInfo[i]);
}


很多方法会抛出InterruptedException,例如sleeep,当收到中断后应取消当前的操作并立即返回。

如果线程运行中长时间不调用抛出InterruptedException异常的方法呢?那就必须定期地(periodically)调用Thread.interrupted方法,它可以在当前线程收到一次中断后返回true。例如:
for (int i = 0; i < inputs.length; i++) {
    heavyCrunch(inputs[i]);
    if (Thread.interrupted()) {
        //我们被中断了,不要再嘎吱嘎吱了。
        return;
    }
}

在这个简单的例子中,代码只是简单地测试中断,如果收到中断立即退出。在更复杂的程序中,抛出一个InterruptedException会更有意义:
if (Thread.interrupted()) {
    throw new InterruptedException();
}
这样可以在异常捕获子句(catch clause)里集中编写中断处理代码。

中断状态标记

中断机制通过维护一个叫作中断状态(interrupt status)的内部标记实现。调用Thread.interrupt(此处应为成员方法而非静态方法)将设置这个标记。当线程调用静态方法Thread.interrupted来检查中断时,中断状态被清除(clear)。一个线程调用非静态方法isInterrupted来查询另一个线程的中断状态,它不会改变中断状态标记。

按照惯例,每一个退出时抛出InterruptedException异常的方法都要清除中断状态。然而,很可能中断状态会立刻被另一个线程通过调用interrupt重新设置。

邀请(Joins)

Join方法允许一个线程等待另一个线程执行直到结束。如果一个线程对象的线程正在执行,
t.join();

将导致当前线程暂停执行直到t的线程终止执行。通过join的另外几个重载方法程序员可以指定一个等待时长。然而,和sleep一样,join在计时上依赖于操作系统,所以你不能假定join会确切地等待你指定的时长。

和sleep一样,要求你对join捕获InterruptedException异常,并以退出作为中断响应。

简单线程示例(The SimpleThreads Example)

下面的线程示例给出了这一章节的一些概念。SimpleTreads包含了两个线程。第一个是每个Java程序都有的主线程。主线程使用Runnable对象创建了一个新线程MessageLoop,并等待它执行完毕。如果MessageLoop运行时间过长,主线程将会中断它。

MessageLoop线程不间断地打印消息。如果它在没有打印完所有消息之前收到中断,MessageLoop会打印一条消息并退出。
public class SimpleThreads {

    // Display a message, preceded by
    // the name of the current thread
    static void threadMessage(String message) {
        String threadName =
            Thread.currentThread().getName();
        System.out.format("%s: %s%n",
                          threadName,
                          message);
    }

    private static class MessageLoop
        implements Runnable {
        public void run() {
            String importantInfo[] = {
                "Mares eat oats",
                "Does eat oats",
                "Little lambs eat ivy",
                "A kid will eat ivy too"
            };
            try {
                for (int i = 0;
                     i < importantInfo.length;
                     i++) {
                    // Pause for 4 seconds
                    Thread.sleep(4000);
                    // Print a message
                    threadMessage(importantInfo[i]);
                }
            } catch (InterruptedException e) {
                threadMessage("I wasn't done!");
            }
        }
    }

    public static void main(String args[])
        throws InterruptedException {

        // Delay, in milliseconds before
        // we interrupt MessageLoop
        // thread (default one hour).
        long patience = 1000 * 60 * 60;

        // If command line argument
        // present, gives patience
        // in seconds.
        if (args.length > 0) {
            try {
                patience = Long.parseLong(args[0]) * 1000;
            } catch (NumberFormatException e) {
                System.err.println("Argument must be an integer.");
                System.exit(1);
            }
        }

        threadMessage("Starting MessageLoop thread");
        long startTime = System.currentTimeMillis();
        Thread t = new Thread(new MessageLoop());
        t.start();

        threadMessage("Waiting for MessageLoop thread to finish");
        // loop until MessageLoop
        // thread exits
        while (t.isAlive()) {
            threadMessage("Still waiting...");
            // Wait maximum of 1 second
            // for MessageLoop thread
            // to finish.
            t.join(1000);
            if (((System.currentTimeMillis() - startTime) > patience)
                  && t.isAlive()) {
                threadMessage("Tired of waiting!");
                t.interrupt();
                // Shouldn't be long now
                // -- wait indefinitely
                t.join();
            }
        }
        threadMessage("Finally!");
    }
}


分享到:
评论

相关推荐

    Java-Concurrency-Progamming-Tutorial:BAT华为大厂一线工程师四年磨一剑精心编排 Java 高并发编程案例代码 & 教程 & 面试题集锦。详细文档讲解请阅读本人的知识库仓:https

    2 深入底层原理,实现并发业务 线程N种实现方式 线程启动你真的会么? 线程停止、中断的最佳实践 线程生命周期 趣解Thread和Object类中线程相关方法:wait、notify、join、yield… 线程属性 线程异常处理 线程安全与...

    Java多线程批量数据导入的方法详解

    2. Java线程池:https://docs.oracle.com/javase/tutorial/essential/concurrency/pools.html 相关知识点: 1. Java多线程编程 2. Java线程池 3. 数据导入 4. 数据读取 5. 数据写入 6. 批量处理 7. 多线程处理 8. ...

    Java EE 6 tutorial volume I

    9. **Concurrency Utilities**:新增了线程管理和并发编程的支持。 10. **XML Web Services**:继续支持SOAP和WS-*标准。 11. **Web Sockets**:新增支持Web Sockets协议,实现更高效的客户端与服务器通信。 #### ...

    Akka Concurrency Framework

    Akka设计时考虑了与Java内存模型的兼容性,确保Actor内部的状态更新是线程安全的。 ##### 2.7 消息发送语义 在Akka中,消息的发送是非阻塞的,这意味着发送方不会等待接收方处理消息后返回确认。这种机制有助于...

    haskell-simple-concurrency:Haskell中并发的小例子

    2. **`MVars`和`putMVar`、`takeMVar`** `MVar`是Haskell并发中的一种核心同步工具。`putMVar`用于将值放入`MVar`,而`takeMVar`则用于取出值。当多个线程尝试同时访问同一个`MVar`时,只有一个线程能成功,其他的...

    C++标准程序库(C++ standard library)

    10. **并发编程(Concurrency)**:自C++11起,标准库还包含了线程(thread)、互斥量(mutex)、条件变量(condition_variable)等多线程和同步工具,支持并行和异步编程。 了解并熟练运用C++标准程序库是成为合格...

    asio 开发文档教程

    - **The Proactor Design Pattern: Concurrency Without Threads**:讨论了proactor模式及其如何在不使用线程的情况下实现并发。 - **Threads and Asio线程与asio**:阐述了asio如何与多线程环境协同工作。 - **...

    java8.pdf详细说明java8的使用说明

    12. 并发教程(Concurrency Tutorial): 这一部分将探讨Java 8中关于并发编程的新特性,包括新的执行器框架、同步机制、原子变量、ConcurrentMap等。 13. API示例(API Examples): 包括字符串、数值、算术和文件...

    tutorial-12factor

    **2. 配置(Config)** 配置不应存在于代码中,而应通过环境变量管理。这样可以使得配置易于管理和安全,避免在代码仓库中泄露敏感信息。 **3. 依赖(Dependencies)** 明确声明所有依赖项,并通过锁文件(如npm的...

    ruby 2.0.0 p353稳定版本

    - ** Fibers and Concurrency**:Ruby 2.0 引入了 Fiber,这是一种轻量级线程,允许更好的并发处理,尽管它们并不等同于操作系统级别的线程。 - **Stackless Ruby**:虽然 Ruby 2.0 不是完全的 Stackless Ruby,但...

    The Rust Programming Language.pdf

    8. **并发与线程(Concurrency and Threads)**:Rust的内存安全特性使得编写高效的并发代码变得容易。通过通道、原子操作和共享状态,开发者可以构建安全的多线程程序。 9. **错误处理(Error Handling)**:Rust...

    开发J2EE应用的要领

    - [Swing单线程规则](https://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html) - [JTable文档](https://docs.oracle.com/javase/tutorial/uiswing/components/table.html) - [JTree文档]...

    C++标准库详解

    12. **并发编程(Concurrency)**:从C++11开始,C++标准库提供了线程、互斥锁、条件变量等多线程编程工具,帮助开发者编写高效的并发程序。 通过深入学习和实践这些知识点,开发者能够编写出更加高效、可维护且...

    C++ 书籍推荐 从入门到精通所要看的书籍

    - 内容深入浅出地介绍了C++的核心概念,如类、对象、模板等,并提供了许多实用的编程技巧。 - 适合那些想要深入了解C++内部机制并对语言本身有浓厚兴趣的学习者。 - **适用人群**:对C++有初步了解,希望进一步...

    Apache Shiro 1.2.x 参考手册1

    2. **Tutorial** (教程) 教程部分提供了一个快速入门的指南,帮助新用户理解如何配置和使用 Shiro 的基本功能。这通常包括创建简单的应用程序,设置认证和授权规则,以及处理会话和加密。 3. **Architecture** ...

    C++书籍指南

    - **《C++ Concurrency in Action》**:Anthony Williams编写,本书专注于多线程编程,介绍了如何利用C++11和C++14的新特性实现并发程序设计。对于那些希望了解现代C++并发特性的开发者来说非常有价值。 - **《C++ ...

    swoole-demo

    4. **协程(Coroutine)**:Swoole的一大亮点是其内置的协程支持,协程是一种轻量级的线程,可以在用户态进行上下文切换。在Swoole中,我们可以通过`co::sleep()`、`co::yield()`等函数实现协程的创建和调度,从而...

    golang-tutorials:Go教程-通过编写许多Golang代码让我们真正地变得肮脏

    通过goroutines和channels,Go提供了轻量级线程和高效通信机制。goroutines是低成本的并发执行单元,它们可以在一个进程中轻松创建成千上万个;channels则用于在不同的goroutines之间传递数据,实现同步,避免了传统...

Global site tag (gtag.js) - Google Analytics