线程管理

来源:互联网 发布:大富翁4官方下载mac版 编辑:程序博客网 时间:2024/05/17 04:00

1.线程的创建和运行

  • 继承Thread类,并且覆盖run()方法
  • 创建一个实现Runnable接口的类。使用带参数的Thread构造器来创建Thread对象。这个参数就是实现Runnable接口的类的一个对象
当一个程序的所有线程都运行完成时,更明确地说,当所有非守护线程都运行完成的时候,这个Java程序将宣告结束。如果执行main()方法的线程结束了。其余的线程仍将继续执行到他们运行结束。如果某一个线程调用了System.exit()指令来结束程序的执行,所有的线程都会结束。

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

  • ID:保存了线程的唯一标识符
  • Name:保存了线程的名称
  • Priority:保存了线程对象的优先级。线程的优先级是从1到10,其中1是最低优先级
  • Status:保存了线程的状态。在Java中,线程的状态有6种:new、runnable、blocked、waiting、time waiting或者terminated

3.线程的中断

Thread类有一个表明线程被中断与否的属性,他存放的是布尔值。线程的interrupt()方法被调用时,这个属性就会被设置为true。isInterrupted()方法只是返回这个属性的值。

还有一个方法可以检查线程是否已被中断,即Thread类的静态方法interrupted(),用来检查当前执行的线程是否被中断。
isInterrupted()和interrupted()方法有一个很大的区别。isInterrupted()不能改变interrupted属性的值,但是后者能设置interrupted属性为false。因为interrupted()是一个静态方法,更推荐使用isInterrupted()。

4.线程中断的控制

如果线程实现了复杂的算法并且分布在几个方法中,或者线程里有递归调用的方法,我们就得使用一个更好的机制来控制线程的中断。为了达到这个目的,Java提供了InterruptedException异常。当检查到线程中断的时候,就抛出这个异常,然后在run()中捕获并处理这个异常。

import java.io.File;public class FileSearch implements Runnable{private String initPath;private String fileName;public FileSearch(String initPath, String fileName) {this.initPath = initPath;this.fileName = fileName;}@Overridepublic void run() {File file = new File(initPath);if (file.isDirectory()) {try {directoryProcess(file);} catch (InterruptedException e) {System.out.printf("%s: The search has been interrupted",Thread.currentThread().getName());}}}private void directoryProcess(File file)throws InterruptedException {File[] files = file.listFiles();if (files!=null) {for (int i = 0; i < files.length; i++) {if (files[i].isDirectory()) {directoryProcess(files[i]);}else {fileProcess(files[i]);}}}if (Thread.interrupted()) {throw new InterruptedException();}}private void fileProcess(File file)throws InterruptedException {if (file.getName().equals(fileName)) {System.out.printf("%s : %s\n",Thread.currentThread().getName(),file.getAbsolutePath());}if (Thread.interrupted()) {throw new InterruptedException();}}}

不管递归调用了多少次,只要线程检测到它已经被中断了,就会立即抛出Interrupted异常,然后继续执行run()方法

5.线程的休眠和恢复

sleep()方法接受整型数值作为参数,以表明线程挂起执行的毫秒数。当线程休眠的时间结束了,JVM会分给他CPU时钟,线程将继续执行他的指令。
sleep()方法的另一种使用方式是通过TimeUint枚举类元素进行调用。这个方法也使用Thread类的sleep()方法来使当前线程休眠,但是它接收的参数单位是秒,最后会被转化成毫秒。

import java.util.Date;import java.util.concurrent.TimeUnit;public class FileClock implements Runnable{@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.printf("%s\n",new Date());try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {System.out.printf("The FileClock has been interrupted");}}}}

当调用sleep()方法之后,线程会释放CPU并且不再继续执行任务。在这段时间内,线程不占用CPU时钟,所以CPU可以执行其他的任务。

如果休眠中线程被中断,该方法就会立即抛出InterruptedException异常,而不需要等待到线程休眠时间结束。

  • Java并发API还提供了另一个方法来使线程对象释放CPU,即yield()方法,它将通知JVM这个线程对象可以释放CPU了。JVM并不保证遵循这个要求。通常来说,yield()方法只做调试使用。

6.等待线程的终止

在一些情形下,我们必须等待线程的终止。例如,我们的程序在执行其他的任务时,必须先初始化一些必须的资源。可以使用线程来完成这些初始化任务,等待线程终止,再执行程序的其他任务。
为了达到这个目的,我们使用Thread类的join()方法。当一个线程对象的join()方法被调用时,调用他的线程将被挂起,直到这个线程对象完成他的任务。
Thread thread1 = new Thread(new DataSourcesLoader());Thread thread2 = new Thread(new NetworkConnectionsLoader());thread1.start();thread2.start();try {thread1.join();thread2.join();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.printf("Main : Configuration has been loaded:%s\n", new Date());

运行这个程序时,你会看到两个线程对象是如何运行的。当两个子线程运行结束的时候,主线程对象才会继续运行并打印出最终的信息。

Java提供了另外两种形式的join()方法:
join(long milliseconds)
join(long milliseconds,long nanos)
当一个线程调用其他某个线程的join()方法时,如果使用的是第一种join()方式,那么他不必等到被调用线程运行终止,如果参数指定的毫秒时钟已经到达,他将继续运行.

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

Java里有一种特殊的线程叫做守护线程。这种线程的优先级很低,通常来说,当同一个应用程序里没有其他的线程运行的时候,守护线程才运行。当守护线程是程序中唯一运行的线程时,守护线程执行结束后,JVM也就结束了这个程序。因为这种特性,守护线程通常被用来作为同一程序中普通线程的服务提供者。他们通常是无限循环的,以等待服务请求或者执行线程的任务。

setDaemon()方法只能在start()方法被调用之前设置。一旦线程开始运行,将不能再修改守护状态。

isDaemon()方法被用来检查一个线程是不是守护线程,返回值true表示这个线程是守护线程,false表示这个线程是用户线程。

8.线程中不可控异常的处理

在Java中有两种异常:
  • 非运行时异常(Checked Exception):这种异常必须在方法声明的throws语句指定,或者在方法体内捕获。
  • 运行时异常(Unchecked Exception):这种异常不必再方法声明中指定,也不需要在方法体中捕获。
因为run()方法不支持throws语句,所以当线程对象的run()方法抛出非运行异常时,我们必须捕获并且处理他们。当运行时异常从run()方法中抛出时,默认行为是在控制台输出堆栈记录并且退出程序。

import java.lang.Thread.UncaughtExceptionHandler;public class ExceptionHandler implements UncaughtExceptionHandler{@Overridepublic void uncaughtException(Thread t, Throwable e) {System.out.println("An exception has been captured");System.out.println("Thread : "+t.getId());System.out.println("Exception: "+e.getClass().getName()+": "+e.getMessage());System.out.println("Stack Trace: ");e.printStackTrace(System.out);System.out.println("Thread status: "+t.getState());}}
public class Task implements Runnable{@Overridepublic void run() {int numero = Integer.parseInt("TTT");}}
public class Main {public static void main(String[] args) {Thread thread = new Thread(new Task());thread.setUncaughtExceptionHandler(new ExceptionHandler());;thread.start();}}
当一个线程抛出了异常并且没有被捕获时(这种情况只可能是运行时异常),JVM检查这个线程是否被预置了未捕获异常处理器。如果找到,JVM将调用线程对象的这个方法,并将线程对象和异常作为传入参数。如果线程没有被预置未捕获异常处理器,JVM将打印堆栈记录到控制台,并退出程序。
Thread类还有另一个方法可以处理未捕获到的异常,即静态方法setDefaultUncaughtExceptionHandler()。这个方法在应用程序中为所有的线程对象创建了一个异常处理器。当线程抛出一个未捕获到的异常时,JVM将为异常寻找以下三种可能的处理器。
  • 首先,他查找线程对象的未捕获异常处理器。
  • 如果找不到,JVM继续查找线程对象所在的线程组的未捕获异常处理器。
  • 如果还是找不到,JVM将继续查找默认的未捕获异常处理器。
  • 如果没有一个处理器存在,JVM则将堆栈异常记录打印到控制台,并退出程序。

9.线程局部变量的使用

如果创建的对象是实现了Runnable接口的类的实例,用它作为传入参数创建多个线程对象并启动这些线程,那么所有的线程将共享相同的属性。也就是说,如果你在一个线程中改变了一个属性,所有的线程都会被这个改变影响。
在某种情况下,这个对象的属性不需要被所有线程共享。Java并发API提供了一个很好的机制,即线程局部变量。

import java.util.Date;import java.util.concurrent.TimeUnit;public class SafeTask implements Runnable {private static ThreadLocal<Date> startDate = new ThreadLocal<Date>() {protected Date initialValue(){return new Date();}};@Overridepublic void run() {// TODO Auto-generated method stubSystem.out.println("Starting Thread: "+Thread.currentThread().getId()+" : "+startDate.get());try {TimeUnit.SECONDS.sleep((int)(Math.rint(Math.random()*10)));} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("Thread Finished: "+Thread.currentThread().getId()+" : "+startDate.get());}}
线程局部变量分别为每个线程存储了各自的属性值,并提供给每个线程使用。你可以使用get()方法读取这个值,并用set()方法设置这个值。如果线程是第一次访问线程局部变量,线程局部变量可能还没有为他存储值,这个时候initialValue()方法就会被调用,并且返回当前的时间值。
线程局部变量也提供了remove()方法,用来为访问这个变量的线程删除已经存储的值。Java并发API包含了InheritableThreadLocal类,如果一个线程是从其他某个线程中创建的,这个类将提供继承的值。如果一个线程A在线程局部变量已有值,当他创建其他某个线程B时,线程B的线程局部变量将跟线程A是一样的。你可以覆盖childValue()方法,这个方法用来初始化子线程在线程局部变量中的值。它使用父线程在线程局部变量中的值作为传入参数。

10.线程的分组

Java并发API提供了一个有趣的功能,它能够把线程分组。这允许我们把一组的线程当成一个单一的单元,对组内线程对象进行访问并操作他们。
Java提供ThreadGroup类表示一组线程。线程组可以包含线程对象,也可以包含其他的线程组对象,是一个树形结构。
public class Result {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}}
import java.util.Date;import java.util.Random;import java.util.concurrent.TimeUnit;public class SearchTask implements Runnable{private Result result;public SearchTask(Result result) {this.result = result;}@Overridepublic void run() {// TODO Auto-generated method stubString name = Thread.currentThread().getName();System.out.println("Thread "+name+" : Start");try {doTask();result.setName(name);} catch (InterruptedException e) {// TODO Auto-generated catch blockSystem.out.println("Thread "+name+": Interrupted");return;}System.out.println("Thread "+name+" : End");}private void doTask()throws InterruptedException {Random random = new Random(new Date().getTime());int value = (int)(random.nextDouble()*100);System.out.println("Thread "+Thread.currentThread().getName()+" : "+value);TimeUnit.SECONDS.sleep(value);}}

import java.util.concurrent.TimeUnit;public class Main {public static void main(String[] args) {ThreadGroup threadGroup = new ThreadGroup("Searcher");Result result = new Result();SearchTask searchTask = new SearchTask(result);for (int i = 0; i < 5; i++) {Thread thread = new Thread(threadGroup,searchTask);thread.start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}System.out.println("Number of Threads "+threadGroup.activeCount());System.out.println("Infomation about the Thread Group");threadGroup.list();Thread[] threads =  new Thread[threadGroup.activeCount()];threadGroup.enumerate(threads);for (int i = 0; i < threadGroup.activeCount(); i++) {System.out.println("Thread "+threads[i].getName()+" : "+threads[i].getState());}waitFinish(threadGroup);threadGroup.interrupt();}private static void waitFinish(ThreadGroup threadGroup) {while(threadGroup.activeCount()>19) {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}

11.线程组中不可控异常的处理

建立一个方法来捕获线程组中的任何线程对象抛出的非捕获异常。

public class MyThreadGroup extends ThreadGroup{public MyThreadGroup(String name) {super(name);// TODO Auto-generated constructor stub}@Overridepublic void uncaughtException(Thread t, Throwable e) {System.out.println("The thread "+t.getId()+" has thrown an Exception");e.printStackTrace(System.out);System.out.println("Terminating the rest of the Threads");interrupt();}}

public class Task implements Runnable{@Overridepublic void run() {// TODO Auto-generated method stubint result;Random random = new Random(Thread.currentThread().getId());while(true) {result = 1000/((int)(random.nextDouble()*1000));System.out.println(Thread.currentThread().getId()+" : "+result);if (Thread.currentThread().isInterrupted()) {System.out.println(Thread.currentThread().getId()+" : Interrupted");return;}}}}
public class Main {public static void main(String[] args) {MyThreadGroup threadGroup = new MyThreadGroup("MyThreadGroup");Task task = new Task();for (int i = 0; i < 2; i++) {Thread thread = new Thread(threadGroup,task);thread.start();}}}

12.使用工厂类创建线程

工厂模式是面向对象编程中最常使用的模式之一。他是一个创建者模式,使用一个类为其他的一个或者多个类创建对象。当我们要为这些类创建对象时,不需要使用new构造器,而使用工厂类。
使用工厂类,可以将对象的创建集中化,这样做有以下的好处:
  • 更容易修改类,或者改变创建对象的方式;
  • 更容易为有限资源限制创建对象的数目。例如:我们可以限制一个类型的对象不多于N个
  • 更容易为创建的对象生成统计数据
Java提供了ThreadFactory接口,这个接口实现了线程对象工厂。Java并发API的高级工具类也使用了线程工厂创建线程。
import java.util.ArrayList;import java.util.Date;import java.util.Iterator;import java.util.List;import java.util.concurrent.ThreadFactory;public class MyThreadFactory implements ThreadFactory {private int counter;private String name;private List<String> stats;public MyThreadFactory(String name) {this.counter = 0;this.name = name;this.stats = new ArrayList<String>();}@Overridepublic Thread newThread(Runnable r) {Thread thread = new Thread(r,name+"-Thread_"+counter);counter++;stats.add(String.format("Created thread %d with name %s on %s\n", thread.getId(),thread.getName(),new Date()));return thread;}public String getStats() {StringBuffer buffer = new StringBuffer();Iterator<String> iterator = stats.iterator();while(iterator.hasNext()) {buffer.append(iterator.next());buffer.append("\n");}return buffer.toString();}}
import java.util.concurrent.TimeUnit;public class Task implements Runnable{@Overridepublic void run() {// TODO Auto-generated method stubtry {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
public class Main {public static void main(String[] args) {MyThreadFactory factory = new MyThreadFactory("MyThreadFactory");Task task = new Task();Thread thread;System.out.println("Starting the Threads");for (int i = 0; i < 10; i++) {thread = factory.newThread(task);thread.start();}System.out.println("Factory stats:");System.out.println(factory.getStats());}}
ThreadFactory接口只有一个方法,即newThread,他以Runnable接口对象作为传入参数并且返回一个线程对象。当实现ThreadFactory接口时,必须实现覆盖这个方法。大多数基本的线程工厂类只有一行,即: return new Thread(r);  可以增加一些变化来强化实现方法覆盖。

  • 创建一个个性化线程,如这个范例中使用一个特殊的格式作为线程名,或者通过继承Thread类来创建自己的线程类。
  • 保存新创建的线程的统计数据,如本例。
  • 限制创建的线程的数量
  • 对生成的线程进行验证
使用工厂模式是一个很好的编程实践,但是,如果是通过实现ThreadFactory接口来创建线程,你必须检查代码,以保证所有的线程都是使用这个工厂创建的。