线程管理

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;
	}

	@Override
	public 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{

	@Override
	public 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 block
			e.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{

	@Override
	public 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{

	@Override
	public 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();
		}
	};
	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.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 block
			e.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;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		String name = Thread.currentThread().getName();
		System.out.println("Thread "+name+" : Start");
		try {
			doTask();
			result.setName(name);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			System.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 block
				e.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 block
				e.printStackTrace();
			}
		}
	}
}

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

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

public class MyThreadGroup extends ThreadGroup{
	public MyThreadGroup(String name) {
		super(name);
		// TODO Auto-generated constructor stub
	}

	@Override
	public 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{

	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		int 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>();
	}

	@Override
	public 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{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.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接口来创建线程,你必须检查代码,以保证所有的线程都是使用这个工厂创建的。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值