一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第11天,点击查看活动详情
一、实现方法简介
java创建线程主要有三种实现方法,除下图外还有使用 线程池创建线程。
常用第二种接口实现,因为实现接口的方式比继承类的方式更灵活,也能减少程序之间的耦合度。
二、继承Thread类
Thread类是所有线程类的父类,实现了对线程的抽取和封装 实现步骤:
- 定义一个类,继承Thread类,并重写该类的run方法,run方法体为完成的任务。
- 创建Thread类的对象,即创建子线程
- 用线程对象的start方法启动该线程
创建一个售票系统Demo
package com.example.threaddemo;
//定义一个类,继承Thread类
public class SellTickets extends Thread{
private int count = 100;
//重写该类的run方法,run方法体为完成的任务。
@Override
public void run() {
while (count > 0){
count--;
System.out.println(Thread.currentThread().getName() + "剩余" + count + "张票");
}
}
}
编写测试类
package com.example.threaddemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ThreadDemoApplicationTests {
public static void main(String[] args) {
//创建Thread类的对象,即创建子线程
SellTickets s1 = new SellTickets();
SellTickets s2 = new SellTickets();
SellTickets s3 = new SellTickets();
//用线程对象的start方法启动该线程
s1.start();
s2.start();
s3.start();
}
}
结果
说明三个线程各自执行,并没有先后顺序
三、实现Runnable接口
使用Runnanle接口并启动多线程步骤:
- 编写实现类实现Runnable接口,并重写该类的run方法,run方法体为完成的任务。
- 创建实现了Runnable接口的类的对象
- 使用实现类对象创建子线程
- 调用线程对象的start方法启动线程
售票类实现Runnable接口
package com.example.threaddemo;
//定义一个类,实现Runnable接口
public class SellTickets implements Runnable{
private int count = 100;
//重写该类的run方法,run方法体为完成的任务。
@Override
public void run() {
while (count > 0){
count--;
System.out.println(Thread.currentThread().getName() + "剩余" + count + "张票");
}
}
}
编写测试类
package com.example.threaddemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ThreadDemoApplicationTests {
public static void main(String[] args) {
//创建实现了Runnable接口的类的对象
SellTickets s = new SellTickets();
//使用对象创建子线程
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
Thread t3 = new Thread(s);
//用线程对象的start方法启动该线程
t1.start();
t2.start();
t3.start();
}
}
结果
四、两种方法的比较
- 继承Thread:
优点: 编码简单,要访问当前线程除了使用Thread.currentThread(),还可以使用super关键字
缺点: 因为java是==单继承多实现==,继承了Thread就不能继承其他类
- 实现Runnable
优点: 多实现,可以继承其他类。==多个线程可以共享同一个对象==,适合处理同一资源。 缺点: 稍现复杂,只能使用Thread.currentThread()访问当前线程
五、start()和run()方法的区别
- start()方法会==新建一个线程==,并且让这个线程执行run()方法。
- 调用run()也能正常执行。但是,却不能新建一个线程,而是在当前线程调用run()方法,只是作为一个==普通的方法调用==。
六、通过Callable和Future创建线程
Java提供了Callable接口,该接口是Runnable接口的增强版,Callable接口提供了一个call()方法,可以看作是线程的执行体,但call()方法比run()方法更强大。
- call()方法可以有返回值。
- call()方法可以声明抛出异常。
步骤如下:
- 创建Callable接口的实现类,并实现call()方法,该call()方法将作为该线程的执行体,且该call()方法有返回值,再创建Callable的实例。从Java 8开始,可以直接使用Lamda表达式创建Callable对象。
- 使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
- 使用FutureTask对象作为Thread对象的target创建并启动新线程。
- 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
测试类代码
package com.example.threaddemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
@SpringBootTest
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();
}
}
}
结果
说明
程序先使用使用Lamda表达式创建一个Callable对象,然后将该实例包装成一个FutureTask对象。主线程中当循环变量i等于20时,程序启动以FutrueTask对象为target的线程。程序最后调用FutrueTask对象的get()方法来返回call()方法的返回值——该方法将导致主线程被阻塞,直到call()方法结束并返回为止,所以“子线程的返回值:100”永远是最后输出。