基本的线程机制
1.定义任务
我们知道线程是可以驱动任务的,因此我们需要一种描述任务的方式,一种方式是使用Runnable接口,并实现其run()方法,此方法是没有返回值的。
public class Task1 implements Runnable{
public void run(){ System.out.println("执行任务")}
}
我们实现了run()方法,但此方法并没有特殊之处,他不会产生任何线程的能力,要实现线程的行为,就必须将此任务附着到一个线程上去。
public class Demo {
public static void main (String [] args){
Thread t = new Thread(new Task1());
t.start();
}
}
2.使用Executor
定义完任务,同时我们也能将任务附着到一个线程上去。在我们将任务附着到一个线程上去时,我们显式的创建一个线程对象。在Java SE5/6中启动任务的优选方案是使用Executor对象。此对象的作用是帮你管理Thread对象,使你不用显式的去声明一个Thread对象。
public class CachedThreadPool {
public static void main (String [] args){
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new Task1());
}
}
我们可以看到 表面上我们是通过ExecutorService 对象来执行任务,其实是此对象只是构建了一个能够执行任务的环境。其底层还是通过将任务附着到线程上执行任务。与命令模式是一样的,任务暴露了要执行的方法,而ExecutorService对象 构建恰当的上下文环境来执行此任务。
在上述代码中,我们用到了newCachedThreadPool()方法,此方法会在程序执行的时候通常会创建与所需数量相同的线程,然后在他回收旧线程的时候停止创建新的线程。
同时还有另外两种方法 newFixedThreadPool(),此方法可以限制线程的数量,通过向构造函数中传递整数来规定创建线程的数量,newSingleThreadExecutor()方法就像是线程数量为1的FixedThreadPool。
3.从任务中产生返回值
在上述 中创建的任务是没有返回值的,但有时候我们希望任务执行后能返回一个值。这时我们可以实现Callable接口,在Java SE5 中引入的这个接口是具有类型参数的泛型,
他的类型参数是定义返回值的类型的。但必须通过ExecutorService.submit()方法调用 。
public class Task2 implements Callable<E>{
public <E> call(){
return “返回值”;
}
}
public class Demo {
public static void main(String [] args){
ExecutorService execu = Executors.newCachedThreadPool();
Future ture = execu.submit(new Task2());
ture.get();
}
}
可以看到任务的返回值被封装到Future 对象中,我们可以同过此对象的get()方法将返回值取出;取出的返回值类型为String 类型
4.休眠,让步
前面一直说的是任务如何创建 ,如何将任务附着到一个线程上。现在我们简单来看一下影响任务行为的一种简单方法就是调用sleep()方法,此方法可以将任务终止执行给定的时间。调用方式是在run()方法中 添加Thread.sleep(time); 在Java SE5 中引入了更加显式的sleep()版本,它作为TimeUnit类的一部分,同时此方法可以指定sleep()延迟的时间单位。同时另一种行为是通过影响线程,进而影响任务的执行,那就是yield()方法,此方法是暂停当前正在执行的线程对象(从运行状态,回到可运行状态),并给其他优先级相同的线程执行的机会,但这个方法不是绝对的。你调用的此方法只是让其他线程有了执行的机会,但是否会执行其他线程,你是控制不了的。 刚才我们我们提到了线程的优先级, 是通过setPriority()方法来设置的。
5.后台线程
就像在一部电影中总是少不了群主演员。在程序中也存在这种地位的线程,他们被称之为后台(daemon)线程;这种线程并不属于程序中不可或缺的部分。当所有非后台线程结束时,不管后台线程是否执行完毕,都会被终止。那么设置后台线程的方式是在线程进入可运行状态时通过setDaemo()方法,传递布尔类型的值。来标识是否为后台线程。并且通过后台线程任务产生的线程默认也是后台线程。
6.捕获异常
由于线程的本质特性,使得你不能捕获从线程中抛出的异常。但在Java SE5中引入一个新的接口 Thread.UncaughtExceptionHandler,此接口可以让你在每个Thread对象上附着一个异常处理器通过uncaughtException()方法在线程因未捕获的异常而临近终止时被调用。
class ExceptionThread2 implements Runnable{
public void run(){
Thread t = Thread.currentThread();
System.out.println("run() by "+ t);
System.out.println("eh= "+t.getUncaughtExceptionHandler());
throw new RuntimeException();
}
}
class MyuncaughtException implements Thread.UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread t, Throwable e) {
// TODO Auto-generated method stub
System.out.println("caught "+e);
}
}
class HandlerThreadFactory implements ThreadFactory{
@Override
public Thread newThread(Runnable r) {
// TODO Auto-generated method stub
System.out.println(this.getClass().getSimpleName() + " creating new Thread ");
Thread t = new Thread(r);
System.out.println("created "+ t);
t.setUncaughtExceptionHandler(new MyuncaughtException());
System.out.println("eh= "+ t.getUncaughtExceptionHandler());
return t;
}
}
public class CatureuncaughtException {
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool(new HandlerThreadFactory());
executor.execute(new ExceptionThread2());
}
}