目录
方式三:通过Callable、FutureTask实现有返回结果的多线程
前言
支持多线程是Java语言的一大特点,多线程在实际开发中也有者举足轻重的地位。在说多线程之前先简单提一嘴程序、进程、线程之间的区别。
-
程序:程序即是一段存储在硬盘中的静态代码,等待CPU的调用。
-
进程:程序被调入内存并被CPU执行时便成了进程,可以说进程就是正在运行的程序,是动态的。进程存在生命周期,而且作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
-
线程:“进程是资源分配的最小单位,线程是CPU调度的最小单位”。进程可进一步细化为线程,是一个程序内部的一条执行路径。若一个进程同一时间并行执行多个线程,就是支持多线程的。每个线程拥有独立的运行栈和程序计数器,但一个进程中的多个线程共享相同的内存单元/内存地址空间。
方式一:继承java.lang.Thread类
先来了解一下Thread类常用的几个构造器
//创建新Thread对象
public Thread()
//创建新的Thread对象,并指定线程名
public Thread(String name)
//创建一个新的Thread对象,使其以target作为其运行对象,它实现了Runnable接口中的run方法
public Thread(Runnable target)
//创建一个新的Thread对象,使其以target作为其运行对象,并以指定的名称作为其名称
public Thread(Runnable target, String name)
-
每个线程都是通过某个特定Thread对象的run()方法来执行各种操作的
-
但只有通过该Thread继承类对象的start()方法才能启动这个线程,而非使用run()方法
方式一步骤可以简单概括如下
- 创建子类继承Thread类。
- 重写Thread类中的run方法。
- 创建Thread子类对象,即创建了线程对象。
- 启动多线程调用线程对象start方法:启动线程,调用Thread中的run方法进行操作。
示例代码:
class MultiThread extends Thread{
private String str;
public MultiThread(String str){
this.str=str;
}
@Override
public void run(){//重写Thread类run方法
for(int i=0;i<10;i++){
System.out.println(this.str+": "+i);
}
}
}
我们通过一个测试类,来测试一下多线程
public class ThreadTest {
public static void main(String[] args) {
var thread1 = new MultiThread("线程一");
var thread2 = new MultiThread("线程二");
var thread3 = new MultiThread("线程三");
thread1.start();
thread2.start();
thread3.start();
}
}
打印结果如下:
我们可以清楚的看到不同的线程相互抢占CPU时间片
这里我们说一下线程的优先级
线程的优先级从 1-10,而且Java中分别定义了以下几个常量:
-
MAX_PRIORITY:10
-
NORM_PRIORITY:5
-
MIN _PRIORITY:1
线程调度上实行的是抢占式(os决定,并非Java决定),对于高优先级获得调度的几率大,也就是说低优先级线程并非一定是在高优先级线程之后才被调用。
方式二:实现java.lang.Runnable接口
其实从源码上我们可以看到,Thread类实际上就实现了Runnable接口
而Runnable接口中只有一个run方法,而Thread中重写了这个抽象方法,这里体现了设计模式之一的代理模式。
此时由于我们实现的是Runnable接口,而不再继承Thread父类了,那么对于此时我们定义的 MultiThread 类也就不再支持' start() '这个Thread继承方法
但是不使用start()方法是无法启动多线程,所以我们现在就需要用到public Thread(Runnable target) 这个构造方法了。
方式二步骤简单概括如下
-
定义子类,实现Runnable接口。
-
子类中重写Runnable接口中的run方法。
-
通过Thread类含参构造器创建线程对象。
-
将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
-
调用Thread类的start方法:开启线程,调用run方法执行操作。
示例代码:
class MultiThread implements Runnable{
@Override
public void run(){
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+": "+i);
//通过Thread类静态方法获取当前线程名
}
}
}
public class ThreadTest {
public static void main(String[] args) {
var target = new MultiThread();
//这里我们就创建了一个Runnable实现类对象
var thread1 = new Thread(target);
var thread2 = new Thread(target);
var thread3 = new Thread(target);
//通过setName方法为线程命名
thread1.setName("线程一:");//setName方法设置线程名
thread2.setName("线程二:");
thread3.setName("线程三:");
//启动线程
thread1.start();
thread2.start();
thread3.start();
}
}
结果如下,我们可以看到只需要一个Runnable实现类对象就可以实现多线程
方式三:通过Callable、FutureTask实现有返回结果的多线程
方式三实在jdk 1.5版本后才出现的,与前两种方式相比,最大的不同就是方式三在线程执行完毕后返回值,其中方式三实现多线程的的核心是Callable接口
我们先来瞧一眼源码中怎么写的
可以发现Callbale定义的时候可以设置一个泛型,此泛型的类型就是返回数据的类型,这样的的好处是可以避免向下转型所带来的安全隐患。
与Runnable相比Callable中不再有run方法,而是用带返回值的call方法取代,而且API中也清楚地给出了说明:
“ Callable接口与Runnable类似,两者都是为实例可能由另一个线程执行的类而设计的。但是Runnable不会返回结果,也不能抛出选中的异常。”
在多线程创建上,方式三也与前两种方法有些不同,需要依赖FutureTask类。
FutureTask实现了两个接口:Runnable和Future所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
(实际上FutureTask继承了RunnableFuture接口,RunnableFuture接口继承Runnable和Future,其实它们属于Excutor框架,文档中也说明了FutureTask类Future的一个基本实现类)
方式三步骤简单概括如下:
- 创建一个实现Callable接口的实现类
- 实现call方法,将此线程需要执行的操作声明在call()中
- 创建Callable实现类的对象
- 将Callable接口实现类的对象作为传递到FutureTask类的构造器中,创建FutureTask类的对象
- 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法启动线程
- (可以通过FutureTask的对象' get '方法获取线程中的call的返回值)
示例代码:
class MultiThread implements Callable {
private int sum;
@Override
public Object call() throws Exception {
for(int i=0;i<5;i++) {
sum+=1;
System.out.println(Thread.currentThread().getName() + ": " + i);
}
return sum;//返回累加和,这里运用了自动装箱,int自动包装成Integer类型
}
}
public class ThreadTest {
public static void main(String[] args) {
var multiThread = new MultiThread();
var futureTask1 = new FutureTask(multiThread);
var futureTask2 = new FutureTask(multiThread);
var futureTask3 = new FutureTask(multiThread);
//我们创造了三个FutureTask对象
var thread1 = new Thread(futureTask1);
var thread2 = new Thread(futureTask2);
var thread3 = new Thread(futureTask3);
thread1.setName("线程一");
thread2.setName("线程二");
thread3.setName("线程三");
//启动线程
thread1.start();
thread2.start();
thread3.start();
try {
System.out.println(futureTask1.get());
}catch ( Exception e ){
e.printStackTrace();
}
}
}
与Runnable类似我们只需要创造一个Callable实现类对象,但一个FutureTask对象只能对应个一个线程,运行结果如下。
实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础!
(ps:实现Runnable接口可以避免java中的单继承问题,而且在共享数据上也会比继承Thread方式方便,而与Runnable相比Callbale又可以返回值)
以上就是今天的全部内容啦,小编累了小编要淦饭去了。