1 线程的创建与启动
Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。
1.1 继承Thread类创建线程类
通过继承Thread类来创建并启动多线程的步骤如下:
(1)定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
(2)创建Thread子类的实例,即创建了线程对象。
(3)调用线程对象的start()方法来启动该线程。
注意:
(1)进行多线程编程时,不要忘记了Java程序运行时默认的主线程,main()方法的方法体就是主线程的线程执行体。
(2)使用继承Thread类的方法来创建线程类时,因为程序每次创建线程对象时都需要创建一个Thread子类的实例对象,所以多个线程之间无法共享线程类的实例变量。
1.2 实现Runnable接口创建线程类
使用Runnable接口来创建并启动多线程的步骤如下:
(1)定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体就是该线程的线程执行体。
(2)创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
(3)调用线程对象的start()方法来启动该线程。
注意:
(1)Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。
(2)采用Runnable接口的方式创建的多个线程可以共享线程类的实例变量,这是因为在这种方式下,程序所创建的Runnable对象只是线程的target,而多个线程可以共享同一个target,所以多个线程可以共享同一个线程类(实际上是线程的target类)的实例变量。
1.3 使用Callable和Future创建线程
通过实现Runnable接口创建多线程时,Thread类的作用就是把run()方法包装成线程执行体。
也许受此启发,从Java 5开始,Java提供了Callable接口,这个接口像是Runnable接口的增强,因为Callable接口提供了一个call()方法作为线程执行体,同时call()方法可以有返回值,而且可以声明并抛出异常。
同时Java 5提供了Future接口来代表Callable接口里call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该实现类实现了Future接口,并实现了Runnable接口,也就是说它可以作为Thread类的target。
创建并启动有返回值的线程的步骤如下:
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值,再创建Callable实现类的实例。
(2)使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
1.4 创建线程的三种方式对比
通过继承Thread类或实现Runnable、Callable接口都可以实现多线程,不过实现Runnable接口与实现Callable接口的方式基本相同,只是Callable接口里定义的方法有返回值,可以声明抛出异常而已,所以这两个可以归为一类。那这种方式与继承Thread方式之间的主要差别如下。
采用实现Runnable、Callable接口的方式创建多线程的优缺点:
(1)线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
(2)这种方式下,多个线程可以共享同一个target对象,非常适合多个相同线程处理同一份资源的情况。
(3)劣势是编程稍显复杂,访问当前线程,必须使用Thread.currentThread()方法。
采用继承Thread类的方式创建多线程的优缺点:
(1)编写简单,若需要访问当前线程,无须使用Thread.currentThread()方法,直接使用this即可获得当前线程。
(2)劣势是,线程类已经继承了Thread类,就不能再继承其他父类了。
也是因为上面优劣势,一般是推荐采用实现Runnable接口、Callable接口的方式来创建多线程。对于不需要返回值的就用实现Runnable接口方式,对于需要返回值的就用实现Callable方式来创建。
写在最后的话:
搞开发这么多年了,多线程也用了蛮多了,对于多线程创建和启动的内容也学过很多遍了,但一直局限于应付面试,始终是有点蒙圈的。另外毕竟现在用多线程,都是依赖线程池,我是感觉很少自己直接去new一个线程对象了,所以之前即使有用到,也更多时候都是用的懵懵的。
这次再次学习并整理一下笔记,其实也是受了面试的启发。话说再一次学习,并回顾自己的开发经验,还是很大收获的。总结一下,也方便自己日后再复习回顾吧。