0
点赞
收藏
分享

微信扫一扫

如何创建线程及线程特性

自由的美人鱼 2022-03-18 阅读 68

继承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控制台没有输出异常栈信息,全部异常信息都由异常处理器处理:

 

举报

相关推荐

0 条评论