文章目录
前言
Java目前对于多线程具有三种实现的方式:1.继承Thread 2.实现Runnable接口 3.实现Callable接口,本文章将对这三种方法进行分析
一、继承Thread
1.使用
通过创建一个类去继承Thread,代表了这个类成为了为一个线程的主体类,这个类就可以进行线程的创建了,但是还要覆写父类的run()方法才能执行任务,run()方法代表线程要处理的任务(线程的主方法——类似主程序的main方法)
范例:
public class MyThread extends Thread {
@Override
public void run() {
//设置子线程的名字 测试的时候更直观
setName("我的线程");
for(int x = 0; x< 10 ; x++){
System.out.println(Thread.currentThread().getName()+"------>"+x);
}
}
}
2.测试
在主线程中通过创建MyThread实例化对象调用start()方法创建线程
public class{
public static void main(String[] args) {
//设置主线程名字 更直观观看
Thread.currentThread().setName("主线程");
new MyThread().start();
for(int x = 0;x < 10; x++ ){
System.out.println(Thread.currentThread().getName()+"------>"+x);
}
}
}
测试结果:
从测试的结果可以看出,主线程和子线程是并发执行的,执行的顺序具有随机性。
原因:因为java多线程的实现是抢占时调度——多个线程对CPU的争夺,谁抢到了CPU的使用权,那么抢到的线程才可以运行。ps:优先让优先级高的线程进行运行,如果优先级一样就大家一起抢。
总结:java多线程使用的本质是CPU以人无法察觉的速度对线程进行快速的切换运行,产生了多线程的并发效果
二、实现Runnable接口
1.使用
这种方式的实现也很简单,就是把继承Thread类改为实现Runnable接口。代码如下:
public class MyRunnable implements Runnable {
@Override
public void run() {
for(int x = 0; x< 10 ; x++){
System.out.println(Thread.currentThread().getName()+"------>"+x);
}
}
}
2.测试
代码如下:
将Runnable对象传入Thread类中使用(将任务传入线程),再启动线程
public class ThreadTest{
public static void main(String[] args) {
//设置主线程名字 更直观观看
Thread.currentThread().setName("主线程");
//
new Thread(new MyRunnable(),"子线程").start();
for(int x = 0;x < 10; x++ ){
System.out.println(Thread.currentThread().getName()+"------>"+x);
}
}
}
测试结果:
从测试的结果可以看出,这种方式和第一种方式执行结果是一样的,那么为什么会有第二种方式出现呢?
原因:因为由于继承机制的有局限性,继承只能继承一个父类,无法扩展功能,所以才会产生Runnable接口,特别用来覆写方法run()给Thread类提供任务,而Thread类专门用来产生线程,分工合作。这种方式也更合理,实际上我们应用这种方式也多于第一种方式。
额外知识点: 可以发现从JDK1.8开始,Runnable接口使用了函数式接口定义,所以也可以直接利用Lambda表达式进行线程类的实现定义
范例如下:
public class LambdaTest{
public static void main(String[] args) {
//设置主线程名字 更直观观看
Thread.currentThread().setName("主线程");
//lambda方法格式 () -> {run方法体} 大括号可用 可不用 匿名内部类的简易版
new Thread(() -> {
for(int x = 0; x< 10 ; x++){
System.out.println(Thread.currentThread().getName()+"------>"+x);
}
},"Lambda传入子线程").start();
for(int x = 0;x < 10; x++ ){
System.out.println(Thread.currentThread().getName()+"------>"+x);
}
}
}
3.继承Thread和实现Runnable的对比
1.第一种方法十分简便,直接在继承Thread的类中重写run()方法并直接启动即可,但是具有单继承的局限性缺点
2.而第二种方法复杂了一点,通过实现接口Runnable来实现对线程任务的实现,但是可以多实现,使得开发更加灵活
三、实现Callable接口
由于Runnable接口和Thread都有一个缺点:当线程执行完毕后,我们无法获取一个返回值,并且run()方法也无法抛出异常,所以从JDK1.5之后就提出了一个新的线程实现接口:java.util.concurrent.Callable接口。
1.使用
因为Thread类中没有实现Callable接口,所以实现Callable的类不能像实现Runnable的类一样直接传入参数给Thread类中,但是FutureTask这个类中实现了Runnable并且也可以接收Callable的call方法——类似Runnable的run方法,所以
代码如下:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadTest{
public static void main(String[] args) {
//设置主线程名字 更直观观看
Thread.currentThread().setName("主线程");
//利用lambda方式建立Callable的子类对象
Callable<String> call = () -> {
for(int x = 0; x< 10 ; x++){
System.out.println(Thread.currentThread().getName()+"------>"+x);
}
return "该线程执行成功";
};
//创建FutureTask对象 并传入Callable子对象
FutureTask<String> ft = new FutureTask<>(call);
//创建子线程
new Thread(ft,"Callable传入子线程").start();
//主线程执行
for(int x = 0;x < 10; x++ ){
System.out.println(Thread.currentThread().getName()+"------>"+x);
}
//获取子线程返回的值
try {
String s = ft.get();
System.out.println(s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
2.测试
该线程方式可以获取得到返回值,该图片最后一句“该线程执行成功”就是我得到的返回值
面试题: Runnable 与 Callable的区别?
- Runnable没有返回值;Callable可以返回执行结果
- Callable接口的call()允许抛出异常;Runnable的run()不能抛出
总结
1、任何一个线程的对象都使用Thread类进行封装,线程的启动使用的是都是start(),启动的时候线程进入的是一种就绪状态,并没有执行。
2、进入到就绪状态之后就需要等待进行资源调度,当某一个线程调度成功之后则进入到运行状态(run()方法),但是所有的线程不可能一直持续执行下去,中间需要产生一些暂停状态,例如:阻塞状态,因为线程不可一直占有资源,所以当线程让出资源的时候,该线程就会进入阻塞状态,然后等待下一次调度资源,进行运行
3、当run()方法执行完毕之后,实际上该线程的主要任务也就结束了,那么此时就可以直接进入到停止状态。