Java并发编程指南(一):线程管理

1. 线程的创建和运行:

在Java中,我们有2个方式创建线程:

  • 通过直接继承Thread类,然后覆盖run()方法。
  • 构建一个实现Runnable接口的类, 然后创建一个thread类对象并传递Runnable对象作为构造参数

2.获取和设置线程信息:

Thread类的对象中保存了一些属性信息能够帮助我们来辨别每一个线程,知道它的状态,调整控制其优先级。 这些属性是:

  • ID: 每个线程的独特标识。
  • Name: 线程的名称。
  • Priority: 线程对象的优先级。优先级别在1-10之间,1是最低级,10是最高级。不建议改变它们的优先级,但是你想的话也是可以的。
  • Status: 线程的状态。在Java中,线程只能有这6种中的一种状态: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, 或 TERMINATED.

3. 线程的中断:

一个多个线程在执行的Java程序,只有当其全部的线程执行结束时(更具体的说,是所有非守护线程结束或者某个线程调用System.exit()方法的时候),它才会结束运行。有时,你需要为了终止程序而结束一个线程,或者当程序的用户想要取消某个Thread对象正在做的任务。

Java提供中断机制来通知线程表明我们想要结束它。中断机制的特性是线程需要检查是否被中断,而且还可以决定是否响应结束的请求。所以,线程可以忽略中断请求并且继续运行。

Thread 类还有一个boolean类型的属性来表明线程是否被中断。当你调用线程的interrupt() 方法,就代表你把这个属性设置为 true。 而isInterrupted() 方法仅返回属性值。

Thread 类还有其他可以检查线程是否被中断的方法。例如,这个静态方法interrupted()能检查正在运行的线程是否被中断。
isInterrupted()和interrupted() 方法有着很重要的区别。第一个不会改变interrupted属性值,但是第二个会设置成false。

4. 操作线程的中断机制:

使用抛出InterruptedException异常来控制线程的中断。

5. 线程的睡眠和恢复:

  • Thread类的 sleep() 方法 : 此方法接收一个整数作为参数,表示线程暂停运行的毫秒数。 在调用sleep() 方法后,当时间结束时,当JVM安排他们CPU时间,线程会继续按指令执行。
  • TimeUnit枚举类型的sleep() 方法: 它把接收的参数转换成毫秒调用线程类的 sleep() 方法让当前线程睡眠。

6. 等待线程的终结:

  • Thread 类的join() 方法: 当前线程调用某个线程的这个方法时,它会暂停当前线程,直到被调用线程执行完成。
  • 带参数的join()方法:

join (long milliseconds)
join (long milliseconds, long nanos)

第一种join() 方法, 这方法让调用线程等待特定的毫秒数。例如,如果thread1对象使用代码thread2.join(1000), 那么线程 thread1暂停运行,直到以下其中一个条件发生:
        thread2 结束运行
        1000 毫秒过去了
当其中一个条件为真时,join() 方法返回。
第二个版本的 join() 方法和第一个很像,只不过它接收一个毫秒数和一个纳秒数作为参数。

7. 守护线程的创建和运行:

Java有一种特别的线程叫做守护线程。这种线程的优先级非常低,通常在程序里没有其他线程运行时才会执行它。当守护线程是程序里唯一在运行的线程时,JVM会结束守护线程并终止程序。

根据这些特点,守护线程通常用于在同一程序里给普通线程(也叫用户线程)提供服务。它们通常无限循环的等待服务请求或执行线程任务。它们不能做重要的任务,因为我们不知道什么时候会被分配到CPU时间片,并且只要没有其他线程在运行,它们可能随时被终止。JAVA中最典型的这种类型代表就是垃圾回收器。

只能在start() 方法之前可以调用 setDaemon() 方法。一旦线程运行了,就不能修改守护状态。
可以使用 isDaemon() 方法来检查线程是否是守护线程(方法返回 true) 或者是用户线程 (方法返回 false)。

8. 在线程里处理不受控制的异常:

Java里有2种异常:

  • 检查异常(Checked exceptions): 这些异常必须强制捕获它们或在一个方法里的throws子句中。 例如, IOException 或者ClassNotFoundException。
  • 非检查异常(Unchecked exceptions): 这些异常不用强制捕获它们。例如, NumberFormatException。
在一个线程 对象的 run() 方法里抛出一个检查异常,我们必须捕获并处理他们。因为 run() 方法不接受 throws 子句。当一个非检查异常被抛出,默认的行为是在控制台写下stack trace并退出程序。

幸运的是, Java 提供我们一种机制可以捕获和处理线程对象抛出的非检查异常来避免程序终结。

实现一个类来处理非检查异常。这个类必须实现 UncaughtExceptionHandler 接口并实现在接口内已声明的uncaughtException() 方法。

public class ExceptionHandler implements UncaughtExceptionHandler{
    public void uncaughtException(Thread t, Throwable e) {
      System.out.printf("An exception has been captured\n");
      System.out.printf("Thread: %s\n",t.getId());
      System.out.printf("Exception: %s: %s\n",e.getClass().getName(),e.getMessage());
      System.out.printf("Stack Trace: \n");
      e.printStackTrace(System.out); System.out.printf("Thread status: %s\n",t.getState());
    }
}

使用 setUncaughtExceptionHandler() 方法设置非检查异常 handler 。

thread.setUncaughtExceptionHandler(new ExceptionHandler())
当在一个线程里抛出一个异常,但是这个异常没有被捕获(这肯定是非检查异常), JVM 检查线程的相关方法是否有设置一个未捕捉异常的处理者 。如果有,JVM 使用Thread 对象和 Exception 作为参数调用此方法 。如果线程没有设置非检查异常的处理者, 那么 JVM会把异常的 stack trace 写入操控台并结束任务。

setDefaultUncaughtExceptionHandler() 为应用里的所有线程对象建立异常 handler 。
当一个未捕捉的异常在线程里被抛出,JVM会寻找此异常的3种可能潜在的处理者(handler)。
首先, 它寻找这个未捕捉异常所在线程对象的异常handler。如果这个handler 不存在,那么JVM会在线程对象的ThreadGroup里寻找非捕捉异常的handler。如果此方法不存在,那么 JVM 会寻找默认非捕捉异常handler。
如果没有一个handler存在, 那么 JVM会把异常的 stack trace 写入操控台并结束任务。

9. 使用本地线程变量ThreadLocal:

本地线程变量为每个使用这些变量的线程储存属性值。可以用 get() 方法读取值和使用 set() 方法改变值。 如果第一次你访问本地线程变量的值,如果没有值给当前的线程对象,那么本地线程变量会调用 initialValue() 方法来设置值给线程并返回初始值。

    private static ThreadLocal<Date> startDate= new ThreadLocal<Date>() {
        protected Date initialValue(){
            return new Date();
        }
    };

本地线程类还提供 remove() 方法,删除存储在线程本地变量里的值。

Java 并发 API 包括 InheritableThreadLocal 类提供线程创建线程的值的继承性 。如果线程A有一个本地线程变量,然后它创建了另一个线程B,那么线程B将有与A相同的本地线程变量值。 你可以覆盖 childValue() 方法来初始子线程的本地线程变量的值。 它接收父线程的本地线程变量作为参数。

10. 线程组:

Java并发 API里有个有趣的方法是把线程分组。这个方法允许我们按线程组作为一个单位来处理。例如,你有一些线程做着同样的任务,你想控制他们,无论多少线程还在运行,他们的状态会被一个调用中断。

Java 提供 ThreadGroup 类来组织线程。 ThreadGroup 对象可以由 Thread 对象组成和由另外的 ThreadGroup 对象组成,生成线程树结构。

当你调用Thread 类的构造函数时,传递它作为ThreadGroup对象的第一个参数。

Thread thread = new Thread(threadGroup, searchTask);
使用list()方法打印关于ThreadGroup对象信息。
使用 activeCount()方法来获取活跃的线程个数
使用enumerate()方法来获取与ThreadGroup对象关联的活跃线程的列表。

用interrupt() 方法中断线程组中的所有线程。

ThreadGroup 类储存线程对象和其他有关联的 ThreadGroup 对象,所以它可以访问他们的所有信息 (例如,状态) 和执行全部成员的方法 (例如,中断)。

11. 处理线程组内的不受控制异常:

对于编程语言来说,一个非常重要的事情是提供管理应用出错情况的机制。Java 语言, 作为最现代的编程语言,实现了一个基于异常的机制来管理出错情况,它提供很多种类来表示不同的错误。当检测到一个异常状况时,这些异常会被Java类们抛出。你也可以使用这些异常, 或者实现你自己的异常, 来管理你的类产生的错误。
Java 也提供机制来捕捉和处理这些异常 。有些一定要被捕捉或者使用方法的throws句组再抛出,这些异常称为检查异常(checked exceptions)。有些异常不需要被捕捉,这些称为非检查异常(unchecked exceptions)。

当一个非捕获异常在线程内抛出,JVM会为这个异常寻找3种可能handlers。
首先, 它寻找这个未捕获的线程对象的异常handler,如果这个handler 不存在,那么JVM会在线程对象的ThreadGroup里寻找非捕获异常的handler,如果此方法不存在,那么 JVM 会寻找默认非捕获异常handler。如果没有 handlers存在, 那么 JVM会把异常的 stack trace 写入控制台并结束任务。

12. 用线程工厂创建线程:

在面向对象编程的世界中,工厂模式是最有用的设计模式。它是一个创造模式,还有它的目的是创建一个或几个类的对象的对象。然后,当我们想创建这些类的对象时,我们使用工厂来代替new操作。线程工厂就是一个工厂模式。
有了这个工厂,我们有这些优势来集中创建对象们:

  • 更简单的改变了类的对象创建或者说创建这些对象的方式。
  • 更简单的为了限制的资源限制了对象的创建。 例如, 我们只new一个此类型的对象。
  • 更简单的生成创建对象的统计数据。
Java提供一个接口 ThreadFactory接口去实现一个线程对象工厂。 一些高级java并发API工具类就使用了线程工厂来创建线程。


参考资料:《Java 7 Concurrency Cookbook》

                 《Java 9 Concurrency Cookbook Second Edition》


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值