每个线程的作用是完成一定的任务,实际就是执行一段程序流(一段顺序执行的代码)。Java使用线程执行体来代表这段程序流。
继承 Thread 类创建线程类
创建并启动多线程的步骤如下:
- 1、定义 Thread 类的子类,并重写该类的 run() 方法,该 run() 方法的方法体就代表了线程需要完成的任务。因此把 run() 方法称为
线程执行体
。 - 2、创建 Thread 子类的实例,即创建了线程对象。
- 3、调用线程对象的 start() 方法来启动该线程。
代码示例:
// 通过继承Thread类来创建线程类
public class FirstThread extends Thread {
private int i ;
// 重写run方法,run方法的方法体就是线程执行体
public void run() {
for ( ; i < 100 ; i++ ){
// 当线程类继承Thread类时,直接使用this即可获取当前线程
// Thread对象的getName()返回当前该线程的名字
// 因此可以直接调用getName()方法返回当前线程的名
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args){
for (int i = 0; i < 100; i++){
// 调用Thread的currentThread方法获取当前线程
System.out.println(Thread.currentThread().getName()
+ " " + i);
if (i == 20){
// 创建、并启动第一条线程
new FirstThread().start();
// 创建、并启动第二条线程
new FirstThread().start();
}
}
}
}
Java 程序开始运行后,程序至少会创建一个主线程,主线程的线程执行体不是由 run() 方法确定的,而是由 main() 方法确定的 —— main() 方法的方法体代表主线程的线程执行体。
- Thread.currentThread():currentThread() 是 Thread 类的静态方法,该方法总是返回当前正在执行的线程对象。
- getName():该方法是 Thread 类的实例方法,该方法返回调用该方法的线程名字。
- setName(String name):为线程设置名字,默认情况下:主线程的名字为main,用户启动的多个线程的名字依次为 Thread-0、Thread-1、Thread-n。
实现 Runnable 接口创建线程类
创建并启动多线程的步骤如下:
- 1、定义 Runnable 接口的实现类,并重新该接口的 run() 方法,该 run() 方法的方法体同样是该线程的线程执行体。
- 2、创建 Runnable 实现类的实例,并以此实例作为 Thread 的 target 来创建 Thread 对象,该 Thread 对象才是真正的线程对象。
- 3、调用线程对象的 start() 方法来启动该线程。
代码示例:
// 通过实现Runnable接口来创建线程类
public class SecondThread implements Runnable {
private int i ;
// run方法同样是线程执行体
public void run() {
for ( ; i < 100 ; i++ ) {
// 当线程类实现Runnable接口时,
// 如果想获取当前线程,只能用Thread.currentThread()方法。
System.out.println(Thread.currentThread().getName()
+ " " + i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()
+ " " + i);
if (i == 20) {
SecondThread st = new SecondThread(); // ①
// 通过new Thread(target , name)方法创建新线程
new Thread(st , "新线程1").start();
new Thread(st , "新线程2").start();
}
}
}
}
Runnable 对象仅仅作为 Thread 对象的 target ,Runnable 实现类里包含的 run() 方法仅作为线程体。而实际的线程对象依然是 Thread 实例,只是该 Thread 线程负责执行其target 的 run() 方法。总结来说:FirstThread 直接创建的 Thread 子类即可代表线程对象;SecondThread 创建的 Runnable 对象只能作为线程对象的 target。
采用 Runnable 接口的方式创建的多个线程可以共享线程类的实例变量。
使用 Callable 和 Future 创建线程
Callable 接口提供了一个 call() 方法可以作为线程执行体:
- call() 方法可以有返回值
- call() 方法可以声明抛出异常
Callable 接口是Java5新增的接口,并不是 Runnable 接口的子接口,所以不能直接作为 Thread 的 target。call() 方法并不是直接调用,它是作为线程执行体被调用的。
Java5 提供了 Futurn 接口来代表 Callable 接口里 call() 的返回值,并为 Future 接口提供一个 FutureTask 实现类,该实现类实现了 Futurn 接口,并实现了 Runable 接口 —— 可以作为 Thread 类的 target 。
Futurn 接口里定义了如下方法来控制它关联的 Callable 任务
- boolean cancel(boolean mayInterruptIfRunning):试图取消该 Future 里关联的Callable 任务。
- V get():返回 Callable 任务里 call() 方法的返回值。调用该方法将导致程序阻塞,必须等到子线程结束后才会得到返回值。
- V get(long timeout, TimeUnit unit):返回 Callable 任务里 call() 方法的返回值。该方法让程序最多阻塞 timeout 和 unit 指定的时间,如果经过指定时间后 Callable 任务依然没有返回值,将会抛出 TimeoutException 异常。
- boolean isCancelled():如果在 Callable 任务正常完成前被取消,则返回 true 。
- boolean isDone():如果 Callable 任务已完成,则返回 true 。
Callable 接口有泛型限制,Callable 接口里的泛型形参类型与 call() 方法返回值类型相同。
创建并启动有返回值的多线程的步骤如下:
- 1、创建 Callable 接口的实现类,并实现 call() 方法将作为线程执行体,且该 call() 方法有返回值,再创建 Callable 实现类的实例。Java8 开始,可以直接使用 Lambda 表达式创建 Callable 对象。
- 2、使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
- 3、使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
- 4、调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
代码示例:
public class ThirdThread{
public static void main(String[] args){
// 创建Callable对象
ThirdThread rt = new ThirdThread();
// 先使用Lambda表达式创建Callable<Integer>对象
// 使用FutureTask来包装Callable对象
FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)() -> {
int i = 0;
for ( ; i < 100 ; i++ ){
System.out.println(Thread.currentThread().getName()
+ " 的循环变量i的值:" + i);
}
// call()方法可以有返回值
return i;
});
for (int i = 0 ; i < 100 ; i++){
System.out.println(Thread.currentThread().getName()
+ " 的循环变量i的值:" + i);
if (i == 20){
// 实质还是以Callable对象来创建、并启动线程
new Thread(task , "有返回值的线程").start();
}
}
try{
// 获取线程返回值
System.out.println("子线程的返回值:" + task.get());
}
catch (Exception ex){
ex.printStackTrace();
}
}
}
创建线程的三种方式对比
-
采用 Runnable、Callable 接口的方式创建多线程的优缺点:
- 线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
- 这种方式下,多个线程可以共享同一个 target 对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将 CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
- 劣势是,编程稍稍复杂, 如果需要访问当前线程,则必须使用 Thread.currentThread() 方法。采用继承 Thread 类的方式创建多线程的优缺点。
-
采用继承 Thread 类的方式创建多线程的优缺点:
- 劣势是:线程类已经继承了 Thread 类,所以不能再继承其他父类。
- 优势是:编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
网友评论