多线程(二)
这是本人的java多线程系列的第二篇博客,如果有什么疑问欢迎大家评论区提出讨论。
从创建线程开始
创建线程的方式有三种。
第一种:继承Thread类,在子类中重写run( )方法,然后我们可以创建子类的对象并调用对象的start( )方法。 需要注意的是,调用start()方法后会自动运行run()方法里面的业务逻辑,但是如果我们在主线程中直接通过子类对象调用run( )方法,那么这个run( )方法只能当做是一个普通的pojo对象方法,我们需要调用start( )方法来启动线程,然后在进行run( )方法中的业务逻辑。
第二种:实现Runnable接口并实现它里面的抽象方法run( ),然后创建实现类的对象传入到Thread构造器创建对象,然后调用该对象的一start( )方法。注意点同上,
先看看这两种方式,可能有的人会奇怪,为什么第一种方法调用start( )会执行重写的run( )而第二种方法调用start( )却能执行Runnable接口实现类的run( )方法呢?其实这里可以看看源码,在Thread类中的run( )中有个target变量,如果这个target变量不为空,那么就执行target变量的run( )方法。那么这个target是什么呢?可以看到在构造器中赋值,是我们传入的Runnable实现类的对象。所以第一种我们会调用重写的run( ),而第二种当我们直接调用Thread类实例的start( )方法时,就会调用作为参数传入的Runnable接口实现类的run( )。以下是源码的截图。
这两种方式创建线程我们可以对比下:
1.二者业务逻辑都是写在run( )方法中。
2.Thread实现了Runnable接口。
3.Runnable接口实现类的实例,我们可以通过创建多个Thread对象并传入同个实例后启动线程,来达到数据共享的需求,而继承Thread类的方式创建线程无法达到线程间数据共享。
4.java是单继承的,但是可以实现多个接口。
综合以上几点,我们可以看出方式二(实现Runnable)创建线程会优于方式一(继承Thread)。
第三种:(JDK1.5新增)首先创建一个Callable接口的实现类并实现它的call( )方法,让后将这个实现类的实例对象传到FutureTask构造器中创建一个FutureTask的实例对象,然后将FutureTask的实例对象传入到Thread构造器(因为FutureTask是RunnableFuture的实现类,而RunnableFuture又继承了Runnable和Future接口)创建对象并调用start( )来启动线程。这种方式还有一个特别的地方,可以拥有返回值,调用FutureTask的实例对象的get( )方法,就可以获取返回值。下面我们来看看上面相关的源码。
线程初始化后调用start( )启动线程后是不可逆转的。那如果我们对于同个线程多次调用start会是什么结果呢?
我们边测试边看源码来解析下:
首先看看调用一次start( )的情况:
然后看看调用两次start( )的情况:
为什么会抛出异常呢?接下来我们看看start( )是怎么写的。
我们可以看到start( )在被调用后将首先验证这个线程的状态是否为0新建,如果不是那么将抛出IllegalThreadStateException异常。如果是新建则继续接下来的业务逻辑。
关于线程的生命周期
线程的生命周期我觉得网上画的图会比我自己画的更加具体形象,所以我这里借助了https://blog.csdn.net/pange1991/article/details/53860651中的图
线程主要就有这六种状态:初始(NEW)、运行(RUNNABLE)、等待(WAITING)、超时等待(TIMED_WAITING)、阻塞(BLOCKED)、终止(TERMINATED)。
关于这六个状态转化过程中涉及到的方法,今天讲解了Thread.start( ),接下来会继续讲解。
以上就是本节内容,谢谢大家的阅读,如有错漏,欢迎评论区指正提出😚!