当前位置: 移动技术网 > IT编程>开发语言>Java > 《Java多线程面试题》系列-创建线程的三种方法及其区别

《Java多线程面试题》系列-创建线程的三种方法及其区别

2019年11月20日  | 移动技术网IT编程  | 我要评论

1. 创建线程的三种方法及其区别

1.1 继承thread类

首先,定义thread类的子类并重写run()方法:

package com.zwwhnly.springbootaction.javabase.thread;

public class myfirstthread extends thread {
    @override
    public void run() {
        for (int i = 0; i < 5; i++) {
            system.out.printf("[myfirstthread]输出:%d,当前线程名称:%s\n",
                    i, getname());
        }
    }
}

然后,创建该子类的实例并调用start()方法启动线程:

package com.zwwhnly.springbootaction.javabase.thread;

public class threadtest {
    public static void main(string[] args) {
        system.out.println("主线程开始执行,当前线程名称:" +
                thread.currentthread().getname());

        thread firstthread = new myfirstthread();
        firstthread.start();

        system.out.println("主线程执行结束,当前线程名称:" +
                thread.currentthread().getname());
    }
}

运行结果如下所示:

主线程开始执行,当前线程名称:main

主线程执行结束,当前线程名称:main

[myfirstthread]输出:0,当前线程名称:thread-0

[myfirstthread]输出:1,当前线程名称:thread-0

[myfirstthread]输出:2,当前线程名称:thread-0

[myfirstthread]输出:3,当前线程名称:thread-0

[myfirstthread]输出:4,当前线程名称:thread-0

从运行结果可以看出以下2个问题:

  1. 程序中存在2个线程,分别为主线程main和自定义的线程thread-0。
  2. 调用firstthread.start();,run()方法体中的代码并没有立即执行,而是异步执行的。

查看thread类的源码,可以发现thread类实现了接口runnable:

public class thread implements runnable {
    // 省略其它代码
}

这里是重点,面试常问!

1.2 实现runnable接口(推荐)

首先,定义runnable接口的实现类并实现run()方法:

package com.zwwhnly.springbootaction.javabase.thread;

public class mysecondthread implements runnable {
    @override
    public void run() {
        for (int i = 0; i < 5; i++) {
            system.out.printf("[mysecondthread]输出:%d,当前线程名称:%s\n",
                    i, thread.currentthread().getname());
        }
    }
}

然后,调用thread类的构造函数创建thread实例并调用start()方法启动线程:

package com.zwwhnly.springbootaction.javabase.thread;

public class threadtest {
    public static void main(string[] args) {
        runnable target = new mysecondthread();
        thread secondthread = new thread(target);
        secondthread.start();
    }
}

运行结果如下所示:

主线程开始执行,当前线程名称:main

主线程执行结束,当前线程名称:main

[mysecondthread]输出:0,当前线程名称:thread-0

[mysecondthread]输出:1,当前线程名称:thread-0

[mysecondthread]输出:2,当前线程名称:thread-0

[mysecondthread]输出:3,当前线程名称:thread-0

[mysecondthread]输出:4,当前线程名称:thread-0

可以看出,使用这种方式和继承thread类的运行结果是一样的。

1.3 实现callable接口

首先,定义callable接口的实现类并实现call()方法:

package com.zwwhnly.springbootaction.javabase.thread;

import java.util.random;
import java.util.concurrent.callable;

public class mythirdthread implements callable<integer> {
    @override
    public integer call() throws exception {
        thread.sleep(6 * 1000);
        return new random().nextint();
    }
}

然后,调用futuretask类的构造函数创建futuretask实例:

callable<integer> callable = new mythirdthread();
futuretask<integer> futuretask = new futuretask<>(callable);

最后,调用thread类的构造函数创建thread实例并调用start()方法启动线程:

package com.zwwhnly.springbootaction.javabase.thread;

import java.util.concurrent.callable;
import java.util.concurrent.executionexception;
import java.util.concurrent.futuretask;

public class threadtest {
    public static void main(string[] args) {
        system.out.println("主线程开始执行,当前线程名称:" +
                thread.currentthread().getname());

        callable<integer> callable = new mythirdthread();
        futuretask<integer> futuretask = new futuretask<>(callable);
        new thread(futuretask).start();

        try {
            system.out.println("futuretask.isdone() return:" + futuretask.isdone());

            system.out.println(futuretask.get());

            system.out.println("futuretask.isdone() return:" + futuretask.isdone());
        } catch (interruptedexception | executionexception e) {
            e.printstacktrace();
        }

        system.out.println("主线程执行结束,当前线程名称:" +
                thread.currentthread().getname());
    }
}

运行结果如下所示:

主线程开始执行,当前线程名称:main

futuretask.isdone() return:false

-1193053528

futuretask.isdone() return:true

主线程执行结束,当前线程名称:main

可以发现,使用callable接口这种方式,我们可以通过futuretask.get()获取到线程的执行结果,而之前的2种方式,都是没有返回值的。

注意事项:调用futuretask.get()获取线程的执行结果时,主线程会阻塞直到获取到结果。

阻塞效果如下图所示:

1.4 区别

以下是重点,面试常问!

  1. java中,类仅支持单继承,如果一个类继承了thread类,就无法再继承其它类,因此,如果一个类既要继承其它的类,又必须创建为一个线程,就可以使用实现runable接口的方式。
  2. 使用实现runable接口的方式创建的线程可以处理同一资源,实现资源的共享。
  3. 使用实现callable接口的方式创建的线程,可以获取到线程执行的返回值、是否执行完成等信息。

关于第2点,可以通过如下示例来理解。

假如我们总共有10张票(共享的资源),为了提升售票的效率,开了3个线程来售卖,代码如下所示:

package com.zwwhnly.springbootaction.javabase.thread;

public class saleticketthread implements runnable {
    private int quantity = 10;

    @override
    public void run() {
        while (quantity > 0) {
            system.out.println(quantity-- + " is saled by " +
                    thread.currentthread().getname());
        }
    }
}
public static void main(string[] args) {
    runnable runnable = new saleticketthread();
    thread saleticketthread1 = new thread(runnable);
    thread saleticketthread2 = new thread(runnable);
    thread saleticketthread3 = new thread(runnable);

    saleticketthread1.start();
    saleticketthread2.start();
    saleticketthread3.start();
}

因为3个线程都是异步执行的,因此每次的运行结果可能是不一样,以下列举2次不同的运行结果。

第1次运行结果:

10 is saled by thread-0

8 is saled by thread-0

7 is saled by thread-0

5 is saled by thread-0

9 is saled by thread-1

3 is saled by thread-1

2 is saled by thread-1

1 is saled by thread-1

4 is saled by thread-0

6 is saled by thread-2

第2次运行结果:

10 is saled by thread-0

9 is saled by thread-0

8 is saled by thread-0

7 is saled by thread-0

6 is saled by thread-0

5 is saled by thread-0

3 is saled by thread-0

2 is saled by thread-0

4 is saled by thread-2

1 is saled by thread-1

如果将上面的saleticketthread修改成继承thread类的方式,就变成了3个线程各自拥有10张票,即变成了30张票,而不是3个线程共享10张票。

2. thread类start()和run()的区别

2.1 示例

因为实现runnable接口的优势,基本上实现多线程都使用的是该种方式,所以我们将之前定义的myfirstthread也修改为实现runnable接口的方式:

package com.zwwhnly.springbootaction.javabase.thread;

public class myfirstthread implements runnable {
    @override
    public void run() {
        for (int i = 0; i < 5; i++) {
            system.out.printf("[myfirstthread]输出:%d,当前线程名称:%s\n",
                    i, thread.currentthread().getname());
        }
    }
}

然后仍然沿用之前定义的myfirstthread、mysecondthread,我们先看下调用start()的效果:

package com.zwwhnly.springbootaction.javabase.thread;

public class threadtest {
    public static void main(string[] args) {

        system.out.println("主线程开始执行,当前线程名称:" +
                thread.currentthread().getname());

        thread firstthread = new thread(new myfirstthread());

        runnable target = new mysecondthread();
        thread secondthread = new thread(target);

        firstthread.start();
        secondthread.start();

        system.out.println("主线程执行结束,当前线程名称:" +
                thread.currentthread().getname());
    }
}

运行结果(注意:多次运行,结果可能不一样):

主线程开始执行,当前线程名称:main

[myfirstthread]输出:0,当前线程名称:thread-0

[myfirstthread]输出:1,当前线程名称:thread-0

[mysecondthread]输出:0,当前线程名称:thread-1

主线程执行结束,当前线程名称:main

[mysecondthread]输出:1,当前线程名称:thread-1

[mysecondthread]输出:2,当前线程名称:thread-1

[mysecondthread]输出:3,当前线程名称:thread-1

[mysecondthread]输出:4,当前线程名称:thread-1

[myfirstthread]输出:2,当前线程名称:thread-0

[myfirstthread]输出:3,当前线程名称:thread-0

[myfirstthread]输出:4,当前线程名称:thread-0

可以看出,调用start()方法后,程序中有3个线程,分别为主线程main、thread-0、thread-1,而且执行顺序不是按顺序执行的,存在不确定性。

然后将start()方法修改为run()方法,如下所示:

firstthread.run();
secondthread.run();

此时的运行结果如下所示(多次运行,结果是一样的):

主线程开始执行,当前线程名称:main

[myfirstthread]输出:0,当前线程名称:main

[myfirstthread]输出:1,当前线程名称:main

[myfirstthread]输出:2,当前线程名称:main

[myfirstthread]输出:3,当前线程名称:main

[myfirstthread]输出:4,当前线程名称:main

[mysecondthread]输出:0,当前线程名称:main

[mysecondthread]输出:1,当前线程名称:main

[mysecondthread]输出:2,当前线程名称:main

[mysecondthread]输出:3,当前线程名称:main

[mysecondthread]输出:4,当前线程名称:main

主线程执行结束,当前线程名称:main

可以看出,调用run()方法后,程序中只有一个主线程,自定义的2个线程并没有启动,而且执行顺序也是按顺序执行的。

1.2 总结

以下是重点,面试常问!

  • run()方法只是一个普通方法,调用之后程序会等待run()方法执行完毕,所以是串行执行,而不是并行执行。
  • start()方法会启动一个线程,当线程得到cpu资源后会自动执行run()方法体中的内容,实现真正的并发执行。

3. runnable和callable的区别

在文章前面的章节中(1.2 实现runnable接口 和1.3 实现callable接口),我们了解了如何使用runnable、callable接口来创建线程,现在我们分别看下runable和callable接口的定义,其中,runable接口的定义如下所示:

public interface runnable {
    public abstract void run();
}

callable接口的定义如下所示:

public interface callable<v> {
    v call() throws exception;
}

由此可以看出,runnable和callable的区别主要有以下几点:

  1. runable的执行方法是run(),callable的执行方法是call()
  2. call()方法可以抛出异常,run()方法如果有异常只能在内部消化
  3. 实现runnable接口的线程没有返回值,实现callable接口的线程能返回执行结果
  4. 实现callable接口的线程,可以和futuretask一起使用,获取到线程是否完成、线程是否取消、线程执行结果,也可以取消线程的执行。

4. 源码及参考

源码地址:,欢迎下载。

java中实现多线程的两种方式之间的区别

java thread 的 run() 与 start() 的区别

java runnable与callable区别

callable,runnable比较及用法

runnable和callable的区别和用法

如果觉得文章写的不错,欢迎关注我的微信公众号:「申城异乡人」,所有博客会同步更新。

如果有兴趣,也可以添加我的微信:zwwhnly_002,一起交流和探讨技术。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网