创建线程的三种方式

本文详细介绍了Java中创建线程的三种常见方式:继承Thread类、实现Runnable接口及使用Callable与FutureTask。对比分析了各种方式的特点,如单继承限制、线程池兼容性、返回值与异常处理能力。
摘要由CSDN通过智能技术生成

我们平常接触比较多的创建线程的方式一般有两种,第三种接触的比较少,之前去京东面试被问到过,自己太菜,直接就懵逼了,第三种方式完全不知道,现在统一做个总结。

目录

一、方式一:继承Thread类

二、方式二:实现Runnable接口

三、Callable加FutureTask方式


一、方式一:继承Thread类

这种方式就是我们自己创建一个类然后继承Thread,并覆写Thread中的run()就可以,比较简单,直接看demo:

public class MyThread extends Thread {
    //定义指定线程名称的构造方法,构造方法可以不写,默认使用无参构造   
    public MyThread(String name) {
        //调用父类的String参数的构造方法,指定线程的名称        
        super(name);
    }
    /**
     * 重写run方法,完成该线程执行的逻辑    
     */
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+":正在执行!"+i);
        }
    }
}

测试类:

public class Demo01 {
    public static void main(String[] args) {
        //创建自定义线程对象
        MyThread mt = new MyThread("新的线程!");
        //无参构造
        //MyThread mt = new MyThread();
        //开启新线程        
        mt.start();
        //在主方法中执行for循环
        for (int i = 0; i < 10; i++) {
            System.out.println("main线程!"+i);
        }
    }
}

测试结果:

main线程!0
main线程!1
main线程!2
main线程!3
main线程!4
main线程!5
main线程!6
main线程!7
main线程!8
main线程!9
新的线程!:正在执行!0
新的线程!:正在执行!1
新的线程!:正在执行!2
新的线程!:正在执行!3
新的线程!:正在执行!4
新的线程!:正在执行!5
新的线程!:正在执行!6
新的线程!:正在执行!7
新的线程!:正在执行!8
新的线程!:正在执行!9

二、方式二:实现Runnable接口

方式二应该是我们最熟悉的,平常在开发种如果我们有线程方面的需求,一般都是用这种方式来实现:自定义一个类实现Runnable接口,并实现其中的run()方法,之后new出这个类对应的对象,并把这个对象作为Thread的参数传递进去就可以了:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
}
public class Demo02 {
    public static void main(String[] args) {
        //创建自定义类对象  线程任务对象
        MyRunnable mr = new MyRunnable();
        //创建线程对象
        Thread t = new Thread(mr, "小强");
        t.start();
        for (int i = 0; i < 5; i++) {
            System.out.println("旺财 " + i);
        }
    }
}

测试结果:

旺财 0
旺财 1
旺财 2
旺财 3
旺财 4
小强 0
小强 1
小强 2
小强 3
小强 4

方式一和方式二的区别:

  1. 因为java是单继承模式,所以方式二可以避免方式一单继承的局限性;
  2. 增加程序的健壮性,实现解耦,代码可以被多个线程共享,代码和线程独立;
  3. 线程池中只能放实现了Runnable或Callable接口的线程,不能直接放继承至Thread的类。

Thread和Runnable的关系:

Thread和Runnable的关系我们通过查看源码简单了解一下,首先看一下带Runnable参数的Thread的构造函数:

public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

发现里面调用了一个init()方法,我们跟下去:

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null);
    }

继续跟:

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
    ......
    this.target = target;
    ......
}

这个方法中代码比较多,我们只看相关的部分,没错,我们只看这一行就行。这个this.target就是Thread类里的一个Runnable类型的成员变量,这行代码就是把我们刚开始传进来的Runnable对象赋值给了Thread类中的target变量,而这个变量也是一个Runnable类型的。继续往下看代码,有这么一行:

@Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

着就是Thread中的run()方法,我们可以看到,它首先会判断target是否为空(这个target就是我们上面将的那个Thread类中的成员变量),不为空的话就会调用tartget的run方法,也就是接口Runnable的run方法。到这里我们基本就理清方式一和方式二在创建线程时的联系了:如果我们用方式一继承Thread类的方式来创建线程,我们覆写Thread里面的run方法,也就是上面代码的那个部分,这个时候线程启动后就直接执行的是我们覆写过的run方法;如果我们用的是方式二实现Runnable接口的方式创建线程,那么线程启动后也会走Thread本身的run方法,只不过是在这个run方法内部又进一步调用了Runnable接口的run方法来执行具体的逻辑,而这个Runnable接口的run方法就是我们在实现Runnable接口时,必须实现的那个方法。

三、Callable加FutureTask方式

方式三其实是对方式二的进一步加强,所以在创建的时候和方式二有相似之处,方式二需要我们自定义一个类实现Runnable接口并实现其中的run方法来创建线程任务,最后把这个线程任务作为参数传递给Thread;而方式三是需要我们:

1.自定义一个类实现Callable接口并实现其中的call方法来创建线程任务(RunnableCallablerun()方法对call()方法);

2.接着把这个线程任务以参数的形式传递给FutureTask,并new出一个FutureTask对象;

3.最后将这个FutureTas对象继续以参数的形式传递给Thread。

通过上面的步骤我们可以看出,相比方式二就是多了一步步骤2的过程。方式三与方式二相比,方式二中的run方法中不能有返回值,也不可在run方法中抛出异常,而方式三的call方法却可以有返回值,也可以抛出异常,这就是方式三相对与方式二加强的点。抛出异常很好理解,方法在执行的过程中有时候难免会抛出异常,所以方式三在方式二的基础上做了相关的完善。可能你会问那有返回值有什么用呢,这一点也很好理解,现在假如有两个线程A和B,而线程B在执行的时候需要用到线程A运行结束后的执行结果,那这个时候我们就可以用方式三来实现线程A和B,将线程A执行结束后返回的结果保存起来,在线程B运行的时候,把这个结果传给它就可以了。下面我们来看方式三的Demo:

/**
 * <Integer>这个泛型就表示call方法返回值得类型,如果你call方法返回的是字符串
 * 那你就在这里写<String>,因为我这里是返回的100,s所以就写的<Integer>
 */
public class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName() +":call方法执行了...");
        return 100;
    }
}
public class Demo3 {
    public static void main(String[] args) {
        //步骤1
        MyCallable myCallable = new MyCallable();
        //步骤2。同样这里的泛型<Integer>也指的是call方法的返回值类型
        FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
        //步骤3
        Thread threadA = new Thread(futureTask,"threadA");
        //开启线程
        threadA.start();

        try {
            /**
             * 这个FutureTask的get()方法的作用就是获取线程A的返回值;
             * 它是一个阻塞方法,即在那个线程中调用,那个线程就会阻塞,直到线程执行完了并返回结果
             * 后。我们现在在主线程中调用了这个方法,所以在线程A执行完毕并返回结果之前,主线程会一直
             * 阻塞这这里,直到线程A执行完后,下面的代码才可执行,并达到线程A的执行结果
             */
            Integer num = futureTask.get();
            //在主线程中打印从线程A中返回的值
            System.out.println(Thread.currentThread().getName() +":在主线程中打印从线程A中返回的值num=" + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}

输出结果:

threadA:call方法执行了...
main:在主线程中打印从线程A中返回的值num=100

通过输出结果的顺序我们可以看到,线程A先执行完后,主线程才执行,并且在主线程中拿到了线程A的执行结果。

到此,线程的三种方式就讲解完毕了。

线程可以通过以下三种方式创建: 1. 实现Runnable接口,并实现run()方法。首先,自定义一个类并实现Runnable接口,然后在该类中实现run()方法。接下来,创建Thread对象,并将实现了Runnable接口的对象作为参数实例化该Thread对象。最后,调用Thread的start()方法来启动线程。这种方式的代码示例可以参考引用\[1\]中的代码。 2. 继承Thread类,重写run()方法。Thread类本质上也是实现了Runnable接口的一个实例,它代表了一个线程的实例。通过继承Thread类并重写run()方法,然后调用start()方法来启动线程。这种方式的代码示例可以参考引用\[2\]中的代码。 3. 使用匿名内部类实现多线程。可以直接在创建Thread对象时使用匿名内部类来实现多线程。这种方式可以简化代码,但只能使用一次。具体的代码示例可以参考引用\[3\]中的代码。 总结起来,多线程创建方式有实现Runnable接口、继承Thread类和使用匿名内部类。每种方式都有其适用的场景,可以根据具体需求选择合适的方式创建线程。 #### 引用[.reference_title] - *1* *2* [初识多线程——创建线程三种方式](https://blog.csdn.net/pizssn/article/details/106382025)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [多线程创建三种方式](https://blog.csdn.net/qq_31780525/article/details/54338341)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值