java实现线程的方式_Java多线程1:使用多线程的几种方式以及对比

前言

Java多线程的使用有三种方法:继承Thread类、实现Runnable接口和使用Callable和Future创建线程,本文将对这三种方法一一进行介绍。

1、继承Thread类

实现方式很简单,只需要创建一个类去继承Thread类然后重写run方法,在main方法中调用该类实例对象的start方法即可实现多线程并发。代码:

public class MyThread extends Thread {

@Override

public void run(){

super.run();

System.out.println("执行子线程...");

}

}

测试用例:

public class Test {

public static void main(String[] args) {

MyThread myThread = new MyThread();

myThread.start();

System.out.println("主线程...");

}

}

运行结果:

50d727be6456cee0aea28ac606619988.png

当然,这里的结果不代表线程的执行顺序,线程是并发执行的,如果多运行几次,打印顺序可能会不一样。多线程的运行过程中,CPU是以不确定的方式去执行线程的,故运行结果与代码的执行顺序或者调用顺序无关,运行结果也可能不一样。关于线程执行的随机性本文后面也有代码示例。

这里还有一个需要注意的点就是main方法中应该调用的是myThread的start方法,而不是run()方法。调用start()方法是告诉CPU此线程已经准备就绪可以执行,进而系统有时间就会来执行其run()方法。而直接调用run()方法,则不是异步执行,而是等同于调用函数般按顺序同步执行,这就失去了多线程的意义了。

2、实现Runnable接口

这种方式的实现也很简单,就是把继承Thread类改为实现Runnable接口。代码如下:

public class MyRunnable implements Runnable {

@Override

public void run() {

System.out.println("执行子线程...");

}

}

测试用例:

public class Test {

public static void main(String[] args) {

Runnable runnable = new MyRunnable();

Thread thread = new Thread(runnable);

thread.start();

System.out.println("主线程运行结束!");

}

}

运行结果:

cab001cbf97bf24a6c4145c3ff50ae72.png

运行结果没啥好说的,这里main中可以看到真正创建新线程还是通过Thread创建:

Thread thread = new Thread(runnable);

这一步Thread类的作用就是把run()方法包装成线程执行体,然后依然通过start去告诉系统这个线程已经准备好了可以安排执行。

3、使用Callable和Future创建线程

上面的两种方式都有这两个问题:

无法获取子线程的返回值

run方法不可以抛出异常

为了解决这两个问题,我们就需要用到Callable这个接口了。说到接口,上面的Runnable接口实现类实例是作为Thread类的构造函数的参数传入的,之后通过Thread的start执行run方法中的内容。但是Callable并不是Runnable的子接口,是个全新的接口,它的实例不能直接传入给Thread构造,所以需要另一个接口来转换一下。

Java5提供了Future接口来代表Callable接口里call()方法的返回值,并为Future接口提供了一个实现类FutureTask,该实现类的继承关系如图所示:

40dc28b1e0cf2f443fe69fa3d0a8ad85.png

可以看到,该实现类不仅实现了Future接口,还实现了Runnable接口,所以可以直接传给Thread构造函数。

而关于FutureTask的构造函数如下:

2e1cd2774b990061994751292d52f27c.png

所以这里面其实就是要比上一个方法再多一个转换过程,最终一样是通过Thread的start来创建新线程。有了这个思路,代码就很容易理解了:

import java.util.concurrent.Callable;

public class MyCallable implements Callable {

int i = 0;

@Override

public Object call() throws Exception {

System.out.println(Thread.currentThread().getName()+" i的值:"+ i);

return i++; //call方法可以有返回值

}

}

测试:

import java.util.concurrent.Callable;

import java.util.concurrent.FutureTask;

public class Test {

public static void main(String[] args) {

Callable callable = new MyCallable();

for (int i = 0; i < 10; i++) {

FutureTask task = new FutureTask(callable);

new Thread(task,"子线程"+ i).start();

try {

//获取子线程的返回值

System.out.println("子线程返回值:"+task.get() + "\n");

} catch (Exception e) {

e.printStackTrace();

}

}

}

}

执行结果(部分):

42f0243299ed34bf742d5a70eed378f5.png

4、线程执行的随机性

上面介绍第一种方法的时候,说了线程的执行顺序与start()的执行顺序无关,而是CPU有空隙了就过来执行该线程,所以具有随机性,执行顺序也是随机的。

示例代码:

public class MyThread extends Thread {

int i;

public MyThread(int i){

super();

this.i = i;

}

@Override

public void run(){

System.out.println(i);

}

}

测试代码:

public class Test {

Thread thread1 = new MyThread(1);

Thread thread2 = new MyThread(2);

Thread thread3 = new MyThread(3);

Thread thread4 = new MyThread(4);

Thread thread5 = new MyThread(5);

Thread thread6 = new MyThread(6);

Thread thread7 = new MyThread(7);

Thread thread8 = new MyThread(8);

Thread thread9 = new MyThread(9);

Thread thread10 = new MyThread(10);

thread1.start();

thread2.start();

thread3.start();

thread4.start();

thread5.start();

thread6.start();

thread7.start();

thread8.start();

thread9.start();

thread10.start();

}

}

运行结果体现了这一点:

b54f0953f38ddce1901ba561bf893810.png

5、三种方式的对比

第一种和后面两种的对比:

1、通过代码可以看出,第一种方法是最简洁方便的,直接就可以start,不需要任何转换

2、但是第一种有一个很不好的地方就是继承了Thread类后由于java的单继承机制,就不可以继承其他的类了,而如果实现的是接口,就可以实现多个接口,使开发更灵活。

第二种和第三种方式对比:

1、同样的,第二种方法相对第三种方式来说代码更简洁,使用更方便,少了一次转换

2、第三种方法有两个优点:有返回值、可以抛出异常

总结

实际开发中可能有更复杂的代码实现,需要继承其他的类,所以平时更推荐通过实现接口来实现多线程,也就是通过第二或第三种方式来实现,这样能保持代码灵活和解耦。

而选择第二还是第三种方式,则要根据run()方法是不是需要返回值或者捕获异常来决定,如果不需要,可以选择用第二种方式实现,代码更简洁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值