基本的线程机制
一个线程就是进程中的一个单一的顺序控制流,因此,单个进程是可以拥有多个并发执行的任务的。
-
Runnable接口
线程可以驱动任务,但是需要一种描述任务的方式,这个任务可以由Runnable接口来提供。只需要实现Runnable接口并编写Run方法。当用Runnable导出一个类之后,它必须要有run()方法,但是,这个方法并无任何特殊之处,它不会产生任何内在的线程能力,要实现线程行为,必须将Runnable导出的类显式的附着在一个线程上。
代码如下:public class LiftOff implements Runnable { public void run() { int t = 10; while(t-- > 0) { System.out.println(t); Thread.yield(); } } }
-
yield方法
Thread.yield()为静态方法。通常又称作让步方法。调用yield()是对线程调度器的一种建议,暗示线程调度器,该线程已经执行完生命周期中最重要的部分了,此刻可以切换给其他任务执行一段时间了。但是,这并不一定会到之后切换任务。没有任何机制可以保证它将会被采纳。
代码如下:Thread.yield();
-
Thread类
如前所述,将Runnable对象转变为诶工作任务的传统方式就是将其提交给一个Thread构造器。Thread构造器只需要一个Runnable对象。并且,调用Thread.start()会自己调用Runnable对象中的run()方法。在调用Thread.start()会立即返回。并且,尽管start()调用结束了,但是当相应的Thread附着的任务没有run结束之前,该线程无法被垃圾回收器回收。
代码如下:public class BasicThreads { public static void main(String [] args) { Thread t = new Thread(new LiftOff()); t.start(); System.out.println("end"); } }
-
Executor
java.util.coucurren包中执行器Executor将可以为我们管理Thread对象。Executor可以提供ExecutorService,这个ExecutorService知道如何构建恰当的上下文来执行Runnable对象。
一般Executor将提供三种ExecutorService,
A、CachedThreadPool将为每个任务创建一个线程,当前有多少个任务,就创建多少个线程。代码如下:ExecutorService exec = Executors.newCachedThreadPool(); exec .execute(new Runner());
B、FixedThreadPool将一次性预先执行代价高昂的线程分配,因而好处就是节省了时间,不用为每个任务都固定的付出创建线程的开销,也可以控制线程的数量。不会滥用资源。
代码如下:ExecutorService exec = Executors.newFixedThreadPool(5); exec .execute(new Runner());
C、SingleThreadExecutor就像是一个数量为1的FixedThreadPool。如果向这个Executor提交了多个任务,那么这些任务将序列执行。一般用于存活一个连续运行的任务。或者在任何线程中只有唯一的任务可以在运行,比如向使用文件系统,为了保证文件系统的一致性,这里将会使用SingleThreadExecutor。通过序列化任务,可以消除对序列化对象的需求。
代码如下:ExecutorService exec = Executors.newSingleThreadExecutor(); exec .execute(new Runner());
ExecutorService有shutdown()这一函数,这一函数执行之后将无法再向该ExecutorService提交任务,但是当前已提交的任务,将会继续执行,知道所有已提交的任务都执行完了,这个程序才会退出。
-
Callable接口
由于有些任务希望在执行任务完成后返回参数,Runnable接口对象满足不了这个功能,于是就有了Callable接口对象。这个接口可以在完成时返回一个值。与Runnable接口中的run()方法相对应的是Callable接口中的call()方法,并且与Runnable不同的是Runnable接口对象可以任意方式调用运行,但是Callable接口只能使用ExecutorService.submit()方法调用。并且这个submit()方法传入Callable接口对象后,返回的对象是一个Future对象。
Future对象可以用isDone()方法来查询Future是否已经完成。并且可以使用get()来获取值,但是当Future对象没有done之前,调用get()方法将阻塞,直到结果准备就绪。
代码如下:import java.util.concurrent.*; import java.util.*; class TaskWithResult implements Callable<String> { public int id; public TaskWithResult(int id) { this.id = id; } @Override public String call() throws Exception { // TODO Auto-generated method stub return "result of task " + id; } } public class CallableDemo { public static void main(String [] args) { ExecutorService exec = Executors.newCachedThreadPool(); ArrayList<Future<String>> results = new ArrayList<Future<String>>(); for(int i = 0; i < 10; i++) { results.add(exec.submit( new TaskWithResult(i))); } for(Future<String> fs : results) { try { System.out.println(fs.get()); }catch(InterruptedException e) { System.out.println(); return; }catch(ExecutionException e) { System.out.println(e); }finally { exec.shutdown(); } } } }
-
sleep()方法
sleep()方法的调用方式为TimeUnit.MILLISECONDS.sleep(100);
这里的100由MILLSECONDS决定单位为毫秒,有其他选项可以选择。
sleep()方法将使任务终止执行给定的时间,但是并非意味着在走完给定的时间后就会立即返回该线程,这里只是给了一个至少要延迟的时间。sleep()方法的调用可以抛出InterruptedException异常,并且,由于异常不能跨线程传递,所以,如果是任务中的异常必须在任务中自行处理,也就是说必须被run()或者call()捕获。尤其记住,在JAVA SE5之后,sleep()作为TimeUnit类的一部分,需要显式的调用。我们可以称作调用了TimeUnit.MILLSECONDS.sleep(100)的线程被阻塞或者进入睡眠。使得线程调度器必须切换到另一个线程,驱动另一个任务。
代码如下:public void run() { try { int i = 10; while(i-- > 0) { System.out.println(t + " : " + i); TimeUnit.MILLISECONDS.sleep(100); } }catch(InterruptedException e) { System.err.println("Interrupted"); } }
-
优先级
线程潜在的一个选择执行的决定因素是线程的优先级,这个优先级仅是让优先级高的线程执行的频率高于低优先级的线程执行频率。并不会导致死锁。
大多数场景下,所有线程都应该以默认的优先级运行,在此不建议操纵线程的优先级。当然,我们也需要了解这一手段。我们可以在任务中的run()方法中调用setPriority()来设置优先级。并且可以用getPriority()来读取当前线程的优先级。
代码如下:Thread.currentThread().setPriority(priority); Thread.currentThread().getPriority();
当前JDK有10个优先级,也就是说这个priority最多也就设置10种不同的值。值的大小决定了优先级。但是,windows系统只有7个优先级,所以有时候可能当在windows下使用优先级时,不是很准确。同时也可以直接将优先级的值设置为MAX_PRIORITY,NORM_PRIORITY和MIN_PRIORITY。
-
Thread.currentThread()
这里记一个线程的小函数,这个函数可以用来打印线程的名称。它获取的是驱动该任务的Thread对象的引用。
代码如下:System.out.println(Thread.currentThread());
-
后台线程
后台线程(daemon)是指在程序运行时,在后台提供的一种通用服务的线程,并且这种线程并不属于程序中不可或缺的部分。所以,当所有的非后台线程都结束了,程序也就终止了,后台线程也就随之都被杀死,并结束。也就是说,非后台线程才是程序仍然存活的必需品,非后台线程结束,程序将终止,后台线程将会戛然而止。
后台线程的设置必须在线程启动之前,使用setDaemon()方法设置。代码如下:Thread daemon = new Thread(new SimpleDaemons()); daemon.setDaemon(true); daemon.start();
并且,后台线程创建的线程将自动设置为后台线程。后台线程中的finally可能不会执行,因为当非后台线程执行完后,后台线程就会突然终止,将会来不及执行finally语句。JVM会立即关闭所有的后台线程。
-
join()
join顾名思义,将一个线程加入到另一个线程内,比如说A线程在B线程上调用了B.join(),那么A线程将会被挂起,直到B线程结束才恢复。或者在调用join()方法时,带上一个超时参数,这样,如果B线程就算没有结束,等到超时到了,也会返回。并且,调用join()方法可以被中断,做法就是在调用join()方法线程中调用interrupt()方法。当然这时由于中断可能产生InterruptException异常,所以必须使用try-catch子句。
```
class Joinner extends Thread {
private Sleeper sleeper;
public Joinner(String name, Sleeper sleeper) {
super(name);
this.sleeper = sleeper;
start();
}
public void run() {
try {
sleeper.join(1000);
}catch(InterruptedException e) {
System.out.println("Interrupted");
}
System.out.println(getName() + " join completed");
}
}
```
- 捕获异常
由于线程的本质特性,所以一个线程的异常时不能在另外一个线程中去捕获的,这就是说,我们不能捕获从线程中逃逸的异常。我们在一个A线程中起了另一个B线程,然后在A线程中就算写了try-catch子句,也不能处理B线程中的Exception。
不过我们可以通过继承Thread.UncaughtExceptionHandler这个类,然后为即将运行的线程设置这个异常处理过程。setUncaughtExceptionHandler()中的参数传递,继承的异常处理对象即可,然后使用ExecutorService运行即可。
代码如下:
```
class ExceptionThread2 implements Runnable {
public void run() {
Thread t = Thread.currentThread();
System.out.println("run() by " + t);
System.out.println("en = " + t.getUncaughtExceptionHandler());
throw new RuntimeException();
//System.out.println("test");
}
}
class MyUnCaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e) {
System.out.println("caught " + e);
}
}
class HandlerThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
System.out.println(Thread.currentThread());
System.out.println(this + " creating new Thread");
Thread t = new Thread(r);
System.out.println("created " +t);
t.setUncaughtExceptionHandler(new MyUnCaughtExceptionHandler());
System.out.println("eh1 = " + t.getUncaughtExceptionHandler());
return t;
}
}
public class CaptureUncaughtException {
public static void main(String [] args) {
ExecutorService exec = Executors.newCachedThreadPool(new HandlerThreadFactory());
exec.execute(new ExceptionThread2());
}
}
```