2、认识 Java 里的线程

Java程序天生就是多线程,从main方法开始的主线程以及JVM启动的系统线程。启动线程可以通过扩展Thread或实现Runnable接口。Thread和Runnable分别代表线程和任务。Callable用于返回结果,配合Future和FutureTask使用。中止线程推荐使用中断机制,而非过时的stop方法。start()方法使线程进入就绪队列,run()方法执行业务逻辑。
摘要由CSDN通过智能技术生成

认识 Java 里的线程

1、Java 程序天生就是多线程的

	一个 Java 程序从 main()方法开始执行,然后按照既定的代码逻辑执行,看
	
似没有其他线程参与,但实际上 Java 程序天生就是多线程程序,因为执行 main()

方法的是一个名称为 main 的线程。

	而一个 Java 程序的运行就算是没有用户自己开启的线程,实际也有有很多JVM 
	
自行启动的线程,一般来说有:

	[6] Monitor Ctrl-Break //监控 Ctrl-Break 中断信号的
	
	[5] Attach Listener //内存 dump,线程 dump,类信息统计,获取系统属性等
	
	[4] Signal Dispatcher // 分发处理发送给 JVM 信号的线程
	
	[3] Finalizer // 调用对象 finalize 方法的线程
	
	[2] Reference Handler//清除 Reference 的线程
	
	[1] main //main 线程,用户程序入口

	尽管这些线程根据不同的 JDK 版本会有差异,但是依然证明了 Java 程序天生就是
	
多线程的。

2、启动

	刚刚看到的线程都是 JVM 启动的系统线程,我们学习并发编程希望的自己能操控线程,
	
所以我们先来看看如何启动线程。
2.1、启动线程的方式有:
		1》、X extends Thread;,然后 X.start
	// 创建线程对象
	Thread t = new Thread(){
		public void run(){
			// 要执行的任务
		}
	};
	// 启动线程
	t.start();
	
		2》、X implements Runnable;然后交给 Thread 运行
	Runnable runnable = new Runnable(){
		public void run(){
			// 要执行的任务
		}
	};
	// 创建线程对象
	Thread t = new Thread(runnable);
	// 启动线程
	t.start();

 
2.2、Thread 和 Runnable 的区别:
	Thread 才是 Java 里对线程的唯一抽象,Runnable 只是对任务(业务逻辑)
	
的抽象。Thread 可以接受任意一个 Runnable 的实例并执行。
2.3、Callable、Future 和 FutureTask
1》、Runnable:
		是一个接口,在它里面只声明了一个 run()方法,由于 run()方法返回值为 
		
	void 类型,所以在执行完任务之后无法返回任何结果。

2》、Callable:
		位于 java.util.concurrent 包下,它也是一个接口,在它里面也只声明了
		
	一个方法,只不过这个方法叫做 call(),这是一个泛型接口,call()函数返回的
	
	类型就是传递进来的 V 类型。
	
3》、Future:
		就是对于具体的 Runnable 或者 Callable 任务的执行结果进行取消、查
		
	询是否完成、获取结果。必要时可以通过 get 方法获取执行结果,该方法会阻塞
	
	直到任务返回结果。

在这里插入图片描述

	因为 Future 只是一个接口,所以是无法直接用来创建对象使用的,因此就
	
有了下面的 FutureTask。

在这里插入图片描述

4》、FutureTask:
	 实现了 RunnableFuture 接口,RunnableFuture 继承了 Runnable
	 
接口和 Future 接口,而 FutureTask 实现了 RunnableFuture 接口。所以

它既可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 

的返回值。

在这里插入图片描述

	因此我们通过一个线程运行 Callable,但是 Thread 不支持构造方法中
	
传递Callable 的实例,所以我们需要通过 FutureTask 把一个 Callable 

包装成 Runnable,然后再通过这个 FutureTask 拿到 Callable 运行后的

返回值。

	要 new 一个 FutureTask 的实例,有两种方法:

在这里插入图片描述

3、中止

3.1、线程自然终止
	要么是 run 执行完成了,要么是抛出了一个未处理的异常导致线程提前结束
3.2、stop
	暂停、恢复和停止操作对应在线程 Thread 的 API 就是 suspend()、resume()
	
和 stop()。但是这些 API 是过期的,也就是不建议使用的。不建议使用的原因主

要有:以 suspend()方法为例,在调用后,线程不会释放已经占有的资源(比如

锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop()方

法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资

源释放工作的机会,因此会导致程序可能工作在不确定状态下。正因为 suspend()、

resume()和 stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方法。
3.3、中断
	安全的中止则是其他线程通过调用某个线程 A 的 interrupt()方法对其进行中
	
断操作, 中断好比其他线程对该线程打了个招呼,“A,你要中断了”,不代表线程 A 

会立即停止自己的工作,同样的 A 线程完全可以不理会这种中断请求。线程通过检查

自身的中断标志位是否被置为 true 来进行响应。

	线程通过方法 isInterrupted()来进行判断是否被中断,也可以调用静态方法
	
Thread.interrupted()来进行判断当前线程是否被中断,不过 Thread.interrupted()

会同时将中断标识位改写为 false。


	如果一个线程处于了阻塞状态(如线程调用了 thread.sleep、thread.join、
	
thread.wait 等),则在线程在检查中断标示时如果发现中断标示为 true,则会在

这些阻塞方法调用处抛出 InterruptedException 异常,并且在抛出异常后会立即

将线程的中断标示位清除,即重新设置为 false。


	不建议自定义一个取消标志位来中止线程的运行。因为 run 方法里有阻塞调
	
用时会无法很快检测到取消标志,线程必须从阻塞调用返回后,才会检查这个取

消标志。这种情况下,使用中断会更好,因为:

	1)、一般的阻塞方法,如 sleep 等本身就支持中断的检查
	
	2)、检查中断位的状态和检查取消标志位没什么区别,用中断位的状态还可以
	
	避免声明取消标志位,减少资源的消耗。

注意:处于死锁状态的线程无法被中断

4、深入理解 run()和 start()

	Thread类是Java里对线程概念的抽象,可以这样理解:我们通过new Thread()
	
其实只是 new 出一个 Thread 的实例,还没有操作系统中真正的线程挂起钩来。

只有执行了 start()方法后,才实现了真正意义上的启动线程。


	从 Thread 的源码可以看到,Thread 的 start 方法中调用了 start0()方法,而
	
start0()是个 native 方法,这就说明 Thread#start 一定和操作系统是密切相关的。


	start()方法让一个线程进入就绪队列等待分配 cpu,分到 cpu 后才调用实现的 
	
run()方法,start()方法不能重复调用,如果重复调用会抛出异常(注意,此处可能

有面试题:多次调用一个线程的 start 方法会怎么样?)。


	而 run 方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方法并
	
没有任何区别,可以重复执行,也可以被单独调用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值