章
目
录
之前我们学习了线程的相关概念,那么你知道Java创建线程的方式有哪些吗?接下来给大家介绍下Java创建线程的四种方式。
1、Java创建线程的4种方式
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
- 线程池创建
1.1 方式1:继承Thread类
步骤:
- 创建一个继承于Thread类的子类
- 重写Thread类的run()方法 -> 此线程执行的操作声明在方法体中
- 创建当前Thread子类的对象
- 通过实例对象调用start()方法,启动线程 -> Java虚拟机会调用run()方法
public class MyThread extends Thread {
// 共享数据要放在run()方法外边才能被共享且声明为static,否则就是每个线程都会调用run()方法,都会单独拥有一个run()方法里的独享数据,而非共享数据
//eg: static int trick = 100;
//定义指定线程名称的构造方法
public MyThread(String name) {
//调用父类的String参数的构造方法,指定线程的名称
super(name);
}
// 重写run
@Override
public void run() {
for (int i = 10; i > 0; i--) {
System.out.println(Thread.currentThread().getName() + "线程正在运行任务,输出:"+i);
}
}
}
运行,当调用start()方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的run()方法;
注意main()方法是主线程。
public class Test01 {
public static void main(String[] args) {
//创建自定义线程对象1
MyThread mt1 = new MyThread("子线程1");
//开启子线程1
mt1.start();
//创建自定义线程对象2,分别创建对象开启线程,不可以数据共享,若要共享需要创建static变量
MyThread mt2 = new MyThread("子线程2");
//开启子线程2
mt2.start();
}
}
我们还可以通过创建Thread类的匿名子类的匿名对象来运行线程:
// 创建Thread类的匿名子类的匿名对象,并启动线程
new Thread(){
public void run(){
// 执行的操作
}
}.strat(); // 启动线程
1.2 方式2:实现 Runnable 接口
相比继承 Thread 类,Runnable避免了单继承的局限性;run方法只关于运行逻辑,解耦合;Thread 代表线程,Runnable 可运行的任务(线程要执行的代码)。
Runnable把线程和任务分开了,更容易与线程池等高级 API 配合,让任务类脱离了 Thread 继承体系,使用更加灵活。
步骤:
- 创建一个实现Runnable接口的类
- 实现接口中的run()方法 —-> 线程执行的操作声明在此方法中
- 创建实例对象
- 将此对象作为参数传到Thread类的构造器中,创建Thread类的实例
- 通过Thread的实例对象调用strat()方法,启动线程 —-> Java虚拟机会调用run()方法
要知道,虽然实现Runnable,但最终还是通过Thread类来实现的。
创建线程类,实现Runnable接口及run()方法;
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 10; i > 0; i--) {
System.out.println(Thread.currentThread().getName() + "线程正在运行任务,输出:"+i);
}
}
}
创建Runnable实现类的实例,并把实例作为Thread类的Target创建对象,并调用对象start()方法来启动线程;
public class Test002 {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
// 创建启动第1个线程
Thread t1 = new Thread(myRunnable);
t1.start();
// 创建启动第2个线程
Thread t2 = new Thread(myRunnable);
t2.start();
}
}
我们还可以创建Runnable接口的匿名子类的匿名对象来运行线程:
new Thread(new Runnable(){
public void run(){
// 执行的操作
}
}).start(); // 开启线程
1.3 方式3:实现 Callable 接口
与实现Runnable接口类似,但是Callable是支持返回值的。
创建线程类,实现Callable接口,实现call方法,与Runnable相比,Callable可以有返回值,返回值通过FutureTask进行封装。
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 10; i > 0; i--) {
System.out.println(Thread.currentThread().getName() + "线程正在运行任务,输出:"+i);
}
return "MyCallable";
}
}
运行Callable线程:
public class Test003 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
FutureTask<String> futureTask1 = new FutureTask<>(myCallable);
Thread thread1 = new Thread(futureTask1);
thread1.start();
System.out.println(futureTask1.get());
}
}
1.4 方式4:通过线程池方式
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,即执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
思路: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。
public class ThreadTest {
static ExecutorService service = Executors.newFixedThreadPool(2);//实例创建的为使用的固定线程的线程池
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("main方法开始");
service.execute(new MyRunnable());//传入一个Runable对象
service.shutdown();//关闭线程池
System.out.println("main方法结束");
}
}
具体关于线程池的使用方法可以参考文章:《Java创建线程池的几种方式具体实现》
2、继承Thread类 和 实现Runnable接口对比
2.1. 共同点:
- 都是通过Thread类中定义的start()来启动线程。
- 都是通过Thread类或其子类的实例对象来创建线程。
2.2. 不同点:
- Thread是继承
- Runnable是实现
2.3. Runnable好处:
- 通过实现的方式,避免了类的单继承的局限性
- 自动共享数据,更适合处理有共享数据的业务逻辑
- 实现了逻辑代码(在run()方法中)和数据(在创建线程的方法中)的分离
3、总结
以上就是Java创建线程的几种方式的具体实现,我们通过相关的示例进行了演示和讲解,希望对你有帮助!