Java高级特性 - 多线程基础(1)使用线程第1关:创建线程第2关:使用 Callable 和 Future 创建线程

目录

第1关:创建线程

头歌知识点总结: 

第2关:使用 Callable 和 Future 创建线程

本题头歌知识点 

本题详解:

第1关:创建线程

package step1;
//请在此添加实现代码
//使用继承Thread类的方式创建一个名为 ThreadClassOne 的类,重写的run方法需要实现输出0-10之间的奇数,输出结果如下:1 3 5 7 9;
/********** Begin **********/
public class ThreadClassOne extends Thread { //创建一个类来继承Thread类
    public void run(){ //重写父类的run 方法
        for(int i=0;i<=10;i++){
            if(i%2 == 1)
                System.out.print(i+" ");
            }
        }
}

//使用实现Runnable接口的方式创建一个名为ThreadClassTwo的类,重写run方法,编写start方法,run方法需要实现打印0-10之间的偶数,输出结果如下:0 2 4 6 8 10
class ThreadClassTwo implements Runnable{
    public void run(){
        for(int i=0;i<=10;i++){
            if(i%2==0) 
                System.out.print(i+" ");
        }
    }
}
/********** End **********/

头歌知识点总结: 

 

相关知识

不知道你有没有发现,截止目前,我们编写的代码都是在main()函数中依照编写代码的顺序从上到下依次运行的。

但是我们平常使用的软件基本都是可以多个任务同时执行的,这其中的运行机制是什么呢?这一小节我们就来探讨。

本小节我们来学习Java中程序是如何同时执行多个任务的。

为了完成本关任务,你需要掌握:

1.什么是线程、什么是进程;

2.如何创建线程。

什么是线程、什么是进程

Java中要同时执行(如果是单核,准确的说是交替执行)多个任务,使用的是多线程,而要理解线程,我们先要了解什么是进程什么是线程。

一般的定义:进程是指在操作系统中正在运行的一个应用程序,线程是指进程内独立执行某个任务的一个单元。

怎么理解呢?

比如说QQ是是一个进程,如果你在和A朋友语音聊天的同时和B朋友打字聊天,同时还在QQ群下载图片,这三个操作就相当于开启了三个线程,可以说有了线程之后我们设计的程序就可以一边执行A操作,一边执行B操作了。

线程和进程有什么区别呢?首先最直观的就是:一个进程可拥有多个线程。 具体比较:

  • **调度 ** 进程拥有资源; 线程是调度和分派的基本单位; 同一进程中线程的切换不会引起进程的切换; 进程间的线程切换则会引起进程切换从而导致资源切换等。

  • **并发性 ** 进程:进程和进程之间可并发执行 ; 线程:除了进程间的并发执行还可以线程之间并发执行; 线程的并发性更高。

  • **拥有资源 ** 线程并不能拥有资源,只有进程才拥有资源。

  • **系统开销 ** 进程创建、切换和撤销都会导致系统为之创建或者回收进程控制卡以及资源,但是线程的创建以及线程间的切换并不会引起系统做这些事儿,所以线程的系统开销明显更小。

如何创建线程

在这里我们主要掌握两种创建线程的方式。

1.继承Thread类;

我们可以使用继承Thread类的方式来创建一个线程。 创建一个类来继承Thread类,重写父类的run方法,就实现了创建我们自己的线程了。之后调用线程的start方法,就算是开启了一个线程了。

示例:

class MyThread extends Thread{
private String name;

public MyThread(String name) {
super();
this.name = name;
}
public void run() {
System.out.println("线程" + name +"开始运行");

for (int i = 0; i < 5; i++) {
System.out.println("线程" + name + "运行" + i);
}
System.out.println("线程" + name + "结束");
}
}

public class Test {
public static void main(String[] args) {
Thread t = new MyThread("T!");
t.start();
Thread t2 = new MyThread("T2");

t2.start();

}
}

运行结果: 线程T!开始运行 线程T2开始运行 线程T!运行0 线程T2运行0 线程T!运行1 线程T2运行1 线程T!运行2 线程T!运行3 线程T!运行4 线程T2运行2 线程T2运行3 线程T2运行4 线程T2结束 线程T!结束

运行这段代码我们会发现,线程是交替运行的,并且每次运行输出的结果都不一样,输出是随机的。

2.实现Runnable接口。

最简单创建线程的方法就是实现一个Runnable接口了,实际上所有的线程都是直接或者间接实现了Runnable接口的,上一个例子中Thread类其实就实现了Runnable接口。

示例:

class MyThread implements Runnable {
private String name;
private Thread mythread;

public MyThread(String name) {
super();
this.name = name;
}

public void run() {

for (int i = 0; i < 5; i++) {
System.out.println("线程" + name + "运行" + i);
}
System.out.println("线程" + name + "结束");
}

public void start() {
System.out.println("线程开始: " + name);
if (mythread == null) {
mythread = new Thread(this, name);
mythread.start();
}
}

}

public class Test {
public static void main(String[] args) {
MyThread t1 = new MyThread("T1");
t1.start();
MyThread t2 = new MyThread("T2");
t2.start();
}
}

运行结果:

线程开始: T1 线程开始: T2 线程T1运行0 线程T2运行0 线程T1运行1 线程T1运行2 线程T1运行3 线程T1运行4 线程T1结束 线程T2运行1 线程T2运行2 线程T2运行3 线程T2运行4 线程T2结束

Java1.5版本之后,还提供了一种创建线程的方式: 通过Callable 和 Future 创建线程,这个我们将在之后的实训中学习到。

创建线程的两种方式对比

  • 实现Runnable创建线程时,线程类只是实现了Runnable接口,还可以继承其他的类。

  • 继承THread类创建线程时,线程类继承了Thread类,不能再继承其他类。不过这种方式编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用this即可获得当前线程。

java程序默认启动的线程

Java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用Java命令执行一个类的时候,实际上都会启动一个jvm,每一个jvm实际在就是在操作系统中启动了一个进程。

编程要求

请仔细阅读右侧代码,根据方法内的提示,在Begin - End区域内进行代码补充,具体任务如下:

  • 使用继承Thread类的方式创建一个名为 ThreadClassOne 的类,重写的run方法需要实现输出0-10之间的奇数,输出结果如下: 1 3 5 7 9

  • 使用实现Runnable接口的方式创建一个名为ThreadClassTwo的类,重写run方法,编写start方法,run方法需要实现打印0-10之间的偶数,输出结果如下: 0 2 4 6 8 10

    public ThreadClassOne (){
            super();
        }
    
    public ThreadClassTwo(){
            super();
        }
    这是一个Java类的构造函数。
    它是一个无参构造函数,因为它没有参数。
    它的作用是调用父类的构造函数,即Thread类的构造函数。
    在Java中,如果没有明确指定调用父类构造函数,则会自动调用父类的无参构造函数。
    因此,这个构造函数可以省略,因为它的作用和默认的无参构造函数是一样的。

第2关:使用 Callable 和 Future 创建线程

package step2; //声明该类所在的包

import java.util.concurrent.Callable; //引入需要使用的类
import java.util.concurrent.FutureTask;

public class Task { //定义task类

	public void runThread(int num) { //定义runThread方法
    //请在此添加实现代码
/********** Begin **********/
// 在这里开启线程 获取线程执行的结果
/*这三句代码的作用是创建一个可在另一个线程中执行的任务,并将其封装在一个FutureTask对象中,最后将该FutureTask对象传递给一个新的线程对象,以便在该线程中执行这个任务*/
ThreadCallable t1 = new ThreadCallable(num); 
FutureTask<Integer> ft1 = new FutureTask<>(t1); 
Thread thread1 = new Thread(ft1,"thread1"); 

thread1.start(); // 启动线程
try{ // 获取线程执行的结果
    System.out.println("线程的返回值为:"+ft1.get());
}catch(Exception e){
    e.printStackTrace();
}
/********** End **********/
	}
}

//请在此添加实现代码
/********** Begin **********/
/* 在这里实现Callable接口及方法 */
class ThreadCallable implements Callable<Integer>    {
    int num;
    ThreadCallable(int num){
        this.num = num;
    }
    ThreadCallable(){

    }
     
    public Integer call() throws Exception{
        return getNum(num);
    }

    private int getNum(int num){
        if(num<3){
            return 1;
        }
        else{
            return getNum(num-1) +getNum(num-2);
        }
    }
}

/********** End **********/

本题头歌知识点 

Java1.5版本开始,就提供了 CallableFuture 来创建线程,这种方式也是在Java程序员面试中经常会被问到的问题。

上一小节介绍了ThreadRunnable两种方式创建线程,不过这两种方式创建线程都有一个缺陷:在执行完任务之后无法获取执行结果。 如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。

而如果使用CallableFuture,通过它们就可以在任务执行完毕之后得到任务执行结果

本小节你需要掌握的知识有:

1.什么是CallableFuture

2.如何通过CallableFuture创建线程。

Callable和Future

它们俩其实挺有意思,在运行的时候各司其职,Callable产生结果Future获取结果

使用步骤如下:

  1. 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值;

  2. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值;

  3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程;

  4. 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。

接下来通过一个示例来学习这两个对象的使用:

public class Test {
public static void main(String[] args) {
CallableThreadTest cts = new CallableThreadTest();
// 接收
FutureTask<Integer> ft = new FutureTask<>(cts);

new Thread(ft, "有返回值的线程").start();
for (int i = 0; i < 30; i++) {
System.out.println( "main" + " 的循环变量i的值:" + i);
}

try {
System.out.println("子线程的返回值:" + ft.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}

class CallableThreadTest implements Callable<Integer> {

public Integer call() throws Exception {
int i = 0;
for (; i < 30; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
return i;
}
}

运行这段程序你应该可以获取到类似如下结果(每次运行的结果不一致): ... ... main 的循环变量i的值:28 main 的循环变量i的值:29 有返回值的线程 23 有返回值的线程 24 有返回值的线程 25 有返回值的线程 26 有返回值的线程 27 有返回值的线程 28 有返回值的线程 29 子线程的返回值:30

由于输出过长,省略了部分结果,可以发现在最后接收到了子线程的返回值。

在实现Callable接口中,此时不再是run()方法了,而是call()方法,此call()方法作为线程执行体,同时还具有返回值!

细心的你会发现这个结果是call函数的返回值,怎么拿到这个返回值的呢?是通过FutureTask拿到的,使用ft.get()方法即可获得线程的返回值,这就是一个简单的使用Callable和Future的过程了。

关于Callable和Future的使用,以及他们的常用函数,我们将会在后续的实训中学习。

本题详解:

(一)

ThreadCallable t1=new ThreadCallable(num);
FutureTask<Integer> ft1=new FutureTask<>(t1);
Thread thread1=new Thread(ft1,"thread1");

这三句代码的作用是创建一个可以在另一个线程中执行的可调用对象,并将其封装在一个FutureTask对象中,最后将该FutureTask对象传递给一个新的线程对象,以便在该线程中执行这个可调用对象。

具体来说:

  1. 第一行代码创建了一个ThreadCallable对象t1,它实现了Callable接口,该接口表示一个可调用的任务,可以在另一个线程中执行,并返回一个结果。

  2. 第二行代码创建了一个FutureTask对象ft1,它是一个可调用的任务,它封装了t1对象,可以在另一个线程中执行,并返回一个结果。FutureTask是一种特殊的RunnableFuture,它表示一个可以取消的异步计算任务,它可以执行Callable或Runnable任务,并保存计算结果。

  3. 第三行代码创建了一个Thread对象thread1,它接收一个FutureTask对象ft1作为参数,并将其封装新的线程中执行,线程的名称是“thread1”。

综上所述,这三句代码的作用是创建一个可在另一个线程中执行的任务,并将其封装在一个FutureTask对象中,最后将该FutureTask对象传递给一个新的线程对象,以便在该线程中执行这个任务。

(二)

try{
	System.out.println("线程的返回值为:"+ft1.get());
}catch(Exception e){
	e.printStackTrace();
}

这段代码的作用是获取线程执行的结果,并将结果打印出来。

具体来说:

  1. 第一行代码调用FutureTask对象ft1的get()方法获取线程的返回值。get()方法是一个阻塞方法,如果线程还没有执行完毕,它会一直阻塞直到线程执行完毕并返回结果

  2. 如果线程执行成功,get()方法会返回线程的返回值,这个返回值的类型是Integer。

  3. 第二行代码将线程的返回值打印出来,以便查看执行结果。

  4. 如果线程执行过程中出现了异常,get()方法会抛出一个异常,这个异常需要在catch块中进行处理。通常情况下,我们会打印异常的堆栈信息,以便查看异常的原因和位置。

因此,这段代码的作用是获取线程执行的结果,并将结果打印出来,同时处理可能出现的异常情况。

(三)

这段代码定义了一个类ThreadCallable,它实现了Callable接口,并指定了泛型参数为Integer,表示线程执行的结果是一个整数。

具体来说:

  1. 类中定义了一个成员变量num,表示要计算斐波那契数列的第几项。

  2. 类中定义了一个构造方法ThreadCallable(int num),用于初始化成员变量num。

  3. 类中定义了另一个构造方法ThreadCallable(),这个构造方法没有参数,什么也不做,可能是为了方便创建对象而定义的。

  4. 类中实现了call()方法,这个方法是Callable接口中的一个方法,表示线程需要执行的任务。在这个方法中,调用了getNum(num)方法计算斐波那契数列的第num项,并将结果返回。

  5. 类中定义了一个私有方法getNum(int num),这个方法用递归的方式计算斐波那契数列的第num项。当num小于3时,返回1;否则,返回getNum(num-1) +getNum(num-2)的结果。

因此,这段代码的作用是定义了一个线程任务,用于计算斐波那契数列的第num项,并将结果作为线程的返回值。这个任务使用递归的方式实现,当num小于3时,返回1;否则,返回前两项的和。

 

### 回答1: 可以使用 Python 的 concurrent.futures 库中的 ThreadPoolExecutor 类来使用 callable future 创建线程。首先,需要创建一个 ThreadPoolExecutor 实例,然后使用 submit() 方法将 callable 包装在一个 future 对象中并提交给线程池执行。例如: ``` from concurrent.futures import ThreadPoolExecutor def my_function(): # do something executor = ThreadPoolExecutor() future = executor.submit(my_function) # do other things result = future.result() # block until the function is complete and return the result ``` 在这种情况下, `my_function` 将在另一个线程中运行,并且可以使用 `future.result()` 来阻塞等待结果并获取返回值。 ### 回答2使用 callable future 创建线程的主要目的是为了充分利用多核 CPU 的能力,加速程序的执行速度。 Callable 是一个接口,通常用于表示一个可调用的对象,即一个函数或者一个方法。通过实现 Callable 接口,并重写 call() 方法来创建一个可调用对象。Future 是一个接口,用于表示一个异步计算的结果,它有三种状态:正在进行、已经完成、已经取消。 Callable Future 结合使用,可以实现异步执行一个任务的功能,即先向线程池提交一个 Callable 对象,线程池会为该对象分配一个执行线程,并返回一个 Future 对象。通过调用 Future 对象的 get() 方法,可以等待该计算完成,从而获取返回值。 使用 Callable Future 创建线程的步骤如下: 1. 创建一个 Callable 对象,并实现 call() 方法,该方法返回一个值。 2. 创建一个 ExecutorService 对象,该对象维护着一个线程池。 3. 向线程池提交 Callable 对象,该方法会返回一个 Future 对象。 4. 通过调用 Future 对象的 get() 方法,可以等待 Callable 对象执行完毕,并获取返回值。 例如,下面的代码创建了一个 Callable 对象,并通过 ExecutorService 对象将其提交到线程池中执行,然后通过 Future 对象获取执行的结果: ``` import java.util.concurrent.*; public class CallableDemo { public static void main(String[] args) throws Exception { // 创建一个 Callable 对象 Callable<String> task = () -> { System.out.println("Task is running..."); Thread.sleep(5000); // 模拟一个耗时的操作 return "Hello, World!"; }; // 创建一个 ExecutorService 对象 ExecutorService executor = Executors.newFixedThreadPool(1); // 向线程池提交 Callable 对象,并返回一个 Future 对象 Future<String> future = executor.submit(task); // 等待任务执行完毕,并获取执行的结果 String result = future.get(); System.out.println(result); // 线程池 executor.shutdown(); } } ``` 上述代码创建了一个 Callable 对象 task,该对象模拟了一个耗时的操作,然后创建了一个 ExecutorService 对象 executor,该对象维护着一个固定大小为 1线程池。接着,向线程池提交 Callable 对象 task,并返回一个 Future 对象 future。最后,通过调用 Future 对象的 get() 方法等待 Callable 对象 task 执行完毕,并获取执行的结果 result。最后,线程池 executor。 ### 回答3: 使用callablefuture创建线程,可以让我们更加灵活地管理线程多线程任务。相比于直接使用Thread类创建线程使用callable可以让我们定义更加灵活的任务,callable既可以返回结果,也可以抛出异常。而使用Future则可以让我们更好地掌控线程的执行状态,比如可以查询某个任务是否已经完成,可以等待某个任务的完成。下面,我将进一步介绍callablefuture的用法。 首先,我们需要了解什么是callablecallable是一个接口,其中包含一个call()方法,call()方法包含了要执行的任务。我们可以使用callable创建一个实现了该接口的类的实例,从而将任务封装起来,以便于将该任务提交给线程池执行。创建callable的基本语法如下: ``` class MyCallable implements Callable<Integer> { public Integer call() throws Exception { // 在这里编写任务代码 } } Callable<Integer> callable = new MyCallable(); ``` 上面的代码中,我们创建了一个实现了Callable接口的类MyCallable,该类包含了任务代码,可以返回一个Integer类型的结果。然后,我们创建了一个callable对象,该对象将用于将任务提交给线程池。 接下来,我们需要介绍futurefuture实现了Future接口,表示一个异步的计算结果。当我们将一个任务提交给线程池执行时,会返回一个Future对象,我们可以通过该对象来监控任务的执行状态,等待任务的完成,并获取任务的结果。创建Future对象的基本语法如下: ``` ExecutorService executor = Executors.newFixedThreadPool(1); Future<Integer> future = executor.submit(callable); ``` 上面的代码中,我们创建了一个线程池executor,将callable对象提交给线程池,并返回了一个future对象,该对象表示了可调用对象的计算结果。 最后,我们需要了解如何使用Future对象监控任务的执行状态获取结果。Future对象提供了一些方法,可以用于查询任务的执行状态获取任务的结果。常用的方法包括: 1. isDone():查询任务是否已经完成; 2. get():等待任务完成,并获取任务的结果,如果任务抛出异常,则该方法将会重新抛出该异常。 完整代码如下: ``` import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class CallableFutureDemo { public static void main(String[] args) throws Exception { // 创建可调用对象 Callable<Integer> callable = new MyCallable(); // 创建线程池 ExecutorService executor = Executors.newFixedThreadPool(1); // 提交任务并获取Future对象 Future<Integer> future = executor.submit(callable); // 查询任务状态 while (!future.isDone()) { System.out.println("任务未完成,等待1秒..."); Thread.sleep(1000); } // 获取任务结果 Integer result = future.get(); System.out.println("任务结果:" + result); // 线程池 executor.shutdown(); } } class MyCallable implements Callable<Integer> { public Integer call() throws Exception { System.out.println(Thread.currentThread().getName() + "开始执行任务..."); Thread.sleep(3000); System.out.println(Thread.currentThread().getName() + "完成任务."); return 100; } } ``` 上面的代码中,我们创建了一个可调用对象MyCallable,并且将该对象提交给了一个线程池executor。然后,我们使用了一个while循环,来等待任务完成。一旦任务完成,我们使用future.get()方法获取了任务的结果,并打印了结果。最后,我们调用了executor.shutdown()方法闭了线程池。 使用callablefuture创建线程,可以让我们更加灵活地管理线程多线程任务,可以有效提高程序的并发效率性能。如果需要使用多线程实现异步的任务执行,可以考虑使用callablefuture
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值