继承Thread类
public class ExtendsThreadTest extends Thread {
@Override
public void run() {
System.out.println("我是新开的线程:" + Thread.currentThread().getName());
// TODO 执行其他业务
}
public static void main(String[] args) {
ExtendsThreadTest test = new ExtendsThreadTest();
Thread t1 = new Thread(test);
t1.start();
System.out.println("我是主线程:" + Thread.currentThread().getName());
// TODO 执行其他主业务
}
}
运行结果可以看到,2个不同的线程并行执行:
实现Runnable接口
public class ImplementsRunnableTest implements Runnable {
@Override
public void run() {
System.out.println("我是新开的线程:" + Thread.currentThread().getName());
// TODO 执行其他业务
}
public static void main(String[] args) {
ImplementsRunnableTest test = new ImplementsRunnableTest();
Thread t1 = new Thread(test);
t1.start();
System.out.println("我是主线程:" + Thread.currentThread().getName());
// TODO 执行其他主业务
}
}
运行结果可以看到,2个不同的线程并行执行:
Callable与Future
可以从上面看到,run()方法是没有返回值的,当我们需要新开的线程有返回值的时候,就可以用Callable接口去实现。
Future接口是用于获取Callable计算结果的,同时由于Thread类不能直接处理Callable接口。其中Future有一个实现类FureTask,它同时实现了Futrue接口和Runnable接口,可以很便利的处理Callable和Future,所以Callable和FureTask总是成对的出现。
public class CallableTest implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("我是新开的线程:" + Thread.currentThread().getName());
// TODO 执行其他业务
// 返回新开线程的执行结果
return 1;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
CallableTest test = new CallableTest();
FutureTask<Integer> task = new FutureTask<Integer>(test);
Thread t1 = new Thread(task);
t1.start();
System.out.println("我是主线程:" + Thread.currentThread().getName());
System.out.println("新开线程执行结果:" + task.get());
// TODO 执行其他主业务
}
}
运行结果可以看到,2个不同的线程并行执行,同时得到了新开线程的运行结果:
Futrue中有以下常用方法:
V get():获取Callable运行结果,该方法会阻塞当前方法所在的线程,直到获取到结果;
V get(long timeout, TimeUnit unit):获取Callable运行结果。与get()的区别在于可以设置一个超时时间,如果在指定超时时间内没有获取到结果,则抛出一个TimeoutException异常;
boolean isCancelled():如果Callable任务在完成前被取消,则返回true;
boolean isDone():如果Callable任务结束,无论是正常结束还是中途取消或异常结束,都返回true。
改造一下代码得到一下结果:
public class CallableTest implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("我是新开的线程:" + Thread.currentThread().getName());
// TODO 执行其他业务
// 延迟3s
Thread.sleep(3000);
// 返回新开线程的执行结果
return 1;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
CallableTest test = new CallableTest();
FutureTask<Integer> task = new FutureTask<Integer>(test);
Thread t1 = new Thread(task);
t1.start();
System.out.println("新开线程是否取消:" + task.isCancelled());
System.out.println("新开线程是否结束:" + task.isDone());
System.out.println("新开线程执行结果:" + task.get());
System.out.println("我是主线程:" + Thread.currentThread().getName());
// TODO 执行其他主业务
}
}
创建过程总结
1.不管是继承Thread,还是实现Runnable,Callable接口,点进源码可以发现,最终执行的都是run()方法,FutureTask只是利用缓存将run()方法执行结果作为返回值返回,作用到Callable接口上;
2.Thread.currentThread()可以返回当前线程对象,可以很方便的在任何地方调用Thread类的非静态方法;
3.不要直接调用run()方法,否则将不会启动新线程;
4.对于run()方法是不能抛出受检查异常的,但是不受检查异常会中断线程执行;
5.Runnable和Callable都是一个函数式接口,可以用lambda表达式创建一个实例:
Runnable r = () -> {
// 这里就是run方法
System.out.println("我是新开的线程:" + Thread.currentThread().getName());
// TODO 执行其他业务
};
Thread t = new Thread(r);
t.start();
// TODO 其他业务代码
注:从java1.8开始,函数式接口都有@FunctionalInterface注解标记。该接口没有任何实际意义,仅用于编译器检测,该注解只能标记在"有且仅有一个抽象方法"的接口上,对于符合函数式标准的接口,加不加该注解都无所谓(为了规范,当然是要加上的好)。可以尝试创建一个用于2个或多个抽象方法的接口,用该注解去标记这个接口是会在编译器报错的。
线程的状态
在java代码中,Thread.state枚举定义了以下几种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED,并且每种引发每种元素的方法都有解释。总结起来就是以下几种状态:
新建状态(NEW):当线程被创建,没有调用start()方法的时候;
可运行状态(RUNNABLE):调用start()方法之后,线程不会立即运行,需要获取到CPU资源之后才会运行(在windows平台可与线程优先级有关);当线程获取到CPU资源之后,线程开始运行(可定义为"运行中状态"),官方没有单独定义此状态的枚举,通过Thread类的getState()方法获取的状态也被义为RUNNABLE,其实该状态也是不存在的,被阻塞的线程重新唤醒之后都是处于"可运行状态",需要重新获取CPU资源;
阻塞状态(BLOCKED/WAITING/TIMED_WAITING):BLOCKED就是等待锁的阻塞,WAITING是需要被手动唤醒的阻塞,比如调用了wait()方法,TIMED_WAITING是带有自动结束时间的阻塞,比如调用了sleep(long millis)方法;
终止状态(TERMINATED):线程运行结束之后的状态(正常运行完毕或异常结束);
线程状态示例图如下:
线程优先级
线程从"可运行状态"到真正开始运行,是需要获取CPU资源的,即优先获取资源的线程优先执行。可以调用Thread类的实例方法setPriority(int newPriority)为线程设置优先级,可设置[1,10]的范围,超过此范围会抛出IllegalArgumentException异常,如下:
同时,Thread类提供了3个优先级常量,MAX_PRIORITY(10)、MIN_PRIORITY(1)和NORM_PRIORITY(5);
注:线程的优先级高度依赖于操作系统,windows平台中,java可以将优先级映射到系统中,但在linux中,线程的优先级会被忽略--所有线程具有相同的优先级;另外,在高优先级的线程未得到执行之前,低优先级的线程永远不会执行,要注意避免资源耗尽的情况。
线程中断
线程终止的标志:run()方法执行完毕,或经由return语句返回,或出现了没有捕获的异常,线程都将被终止。
目前没有任何可以强制中断线程的方法,在历史版本中,Thread类有个stop()方法可以中断线程,但已被废弃。因为中断线程这个操作是及其危险的行为,首先容易造成死锁,其次容易破坏数据的一致性,比如在A到B的转账过程,扣减了A的账户,此时中断线程,如果这里忘记了回滚事务,A账户余额少了,但是B账户没有增加,此时就破坏了数据一致性。
异常处理器
前面说到,在run()方法中,不能抛出受检查异常的,但是不受检查异常会中断线程执行。
因为运行期异常(不受检查异常),是不能确定什么时候出现的,为了更方便的追踪异常,Thread类提供了一个不受检查异常处理器:UncaughtExceptionHandler。
代码示例:
public class UncaughtExceptionHandlerTest {
public static void main(String[] args) {
Runnable r = () -> {
// 模拟运行期异常
throw new RuntimeException("测试异常");
};
Thread t = new Thread(r);
// 创建一个异常处理器
UncaughtExceptionHandler handler = new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("当前线程名称:"+t.getName());
System.out.println(e instanceof RuntimeException);
System.out.println("异常信息:" + e.getMessage());
// TODO 其他处理业务:比如日志收集/系统报警等
}
};
t.setUncaughtExceptionHandler(handler);
t.start();
}
}
从运行结果中可以看到,线程还是会因异常中断,但是console控制台没有输出异常栈信息,全部异常信息都由异常处理器处理: