创建线程的三种方法
1、继承Thread类,重写run()方法,调用start()方法执行。
2、实现Runnable接口,实现run()方法,创建Thread对象时传入Runnable的子类对象,调用start()方法执行。
3、实现Callable接口,实现call()方法,可返回结果并允许此方法抛出异常。实例化FutureTask类时,将实现Callable接口的类的对象作为参数传入,然后实例化Thread类,将FutureTask对象传入,最后执行start()。
JAVA线程类结构的设计:
Callable和Runnable接口是为了那些实例可能被另一个线程执行的类设计的。
多线程执行过程
程序默认的线程执行在栈区,当创建一个新线程时,在栈区开辟一个新的内存空间,执行代码(变量的创建都在新的内存区中)。当线程的代码结束了,线程自动在栈内存中释放了,当所有线程都结束了,那么进程就结束了。
(main方法的线程和main方法中创建的新线程有主从关系吗?main线程会等“子线程”结束再结束吗?)
要注意多线程中的并发执行≠并行执行。(同一时间,多个事件间隔发生【时间片单位】;同一时间,多个事件同时发生。)并发是在多台处理器上同时处理多个任务。所以并发编程的目标是充分的利用处理器的每一个核,以达到最高的处理性能。
并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。
下列Demo中线程任务执行的方法中加睡眠方法的原因是:让线程进入等待状态,方便其他线程抢占资源,这样可以方便的观察到多线程并发执行的情况
Thread
Demo
和Demo2
是两个多线程类,Test
里面测试多线程执行
只有调用start()方法才会创建新线程,直接调用run()方法是不会创建新线程的,只是在main线程中的一个方法顺序执行
Demo.java
1 | package MuliThread.one; |
Demo2.java
1 | package MuliThread.one; |
Test.java
1 | package MuliThread; |
多线程并发执行:
为什么要创建类继承Thread
,而不是直接调用Thread
的start()
方法呢?(只有傻逼才会问这种问题)
Thread类的run()方法没有做事,所以你需要重写run()方法,将你创建新线程需要执行的代码写进去。
匿名内部类
另一种使用是直接创建Thread
1 | package MuliThread.one; |
多线程并发执行:
Runnable
Demo
是线程任务,实现Runnable
接口,实现run()
方法;创建Thread
线程对象,传入Demo
类,调用start()
方法即可。
Demo.java
1 | package MuliThread.two; |
Test.java
1 | package MuliThread.two; |
多线程并发执行:
使用Runnable
的好处:将线程任务和线程对象解耦,更加符合面向对象。(还是不知道有什么实际的好处)
匿名内部类
另一种方式是使用匿名内部类,不用新建类。
1 | package MuliThread.two; |
多线程并发执行:
Callable
要了解Callable,必须了解JAVA的线程体系。
接口Future,Runnable。接口RunnableFuture继承上述两个接口。
类FutureTask实现接口RunnableFuture。
Future
1 | public interface Future<V> { |
Runnable
1 |
|
Thread
1 | public class Thread implements Runnable { |
RunnableFuture
1 | public interface RunnableFuture<V> extends Runnable, Future<V> { |
FutureTask
1 | public class FutureTask<V> implements RunnableFuture<V> { |
类FutureTask有一构造函数,可接受参数Callable类型
Callable
1 |
|
FutureTask类实现了Runnable和Future接口,所以可以把FutureTask当作Runnable,作为参数,放到Thread里面。
所以使用Callable接口创建线程时是这样的步骤:
1、自定义类实现Callable,实现call()方法,里面写线程执行任务代码。
2、创建FutureTask实例,将上述自定义类的实例作为参数传入。
3、创建Thread实例,将上述FutureTask实例作为参数传入。
4、执行Thread的start(),即可创建新线程并执行任务。
代码实现:
Demo
1 | package MuliThread.three; |
Demo2
1 | package MuliThread.three; |
Test
1 | package MuliThread.three; |
多线程并发执行任务:
FutureTask的应用:
FutureTask继承体系的核心是Future接口,Future的核心思想是:一个方法f,计算过程可能非常耗时,等待f返回,显然不明智。可以在调用f的时候,立马返回一个Future,可以通过Future这个数据结构去控制方法f的计算过程。
这里的控制包括:
get方法:获取计算结果(如果还没计算完,也是必须等待的)【重载方法中,可以设置最多等待时间】
cancel方法:还没计算完,可以取消计算过程
isDone方法:判断是否计算完
isCancelled方法:判断计算是否被取消
1 | package MuliThread.three; |
多线程并发执行:
ExecutorService
ExecutorService是一个线程池,他的submit方法,该方法重载了多个方法,分别可接受一个Runnable类型或Callable类型的参数。因此可以自定义类实现Callable或Runnable,然后将此类的实例传入submit,即可开启多线程,执行线程任务。
MyRunnable
1 | package MuliThread.pool; |
MyRunnable
1 | package MuliThread.pool; |
ThreadPoolDemo
1 | package MuliThread.pool; |
多线程并发执行任务:
线程池结合Fature
MyNumCallable
1 | package MuliThread.pool; |
Practice
1 | package MuliThread.pool; |
多线程并发执行:
线程常用方法
Thread
1 | join() //等待该线程终止 |
总结
上述三种方式中,1、2两种方式run()均不能存在返回值,不能抛出异常。
由于1方式是直接继承Thread类,JAVA是单继承,继承此类后就不能继承其他类了,所以比较不灵活。
而2方式是实现接口,一个类实现此接口后也可实现其他接口。
而第三种方式代码没有1、2简洁。
而线程池(ExecutorService)的方式其实还是对其他方法的使用而已。