大飞老师带你再看Java线程(二)

目前java创建线程存在3中方式:
1: 通过继承 Thread 类本身;
2: 通过实现 Runnable 接口;
3: 通过 Callable 和 FutureTask 创建线程

一、通过继承Thread类,并重写run方法实现线程创建
//方式1:继承Thread实现自定义线程
public class MyThread extends  Thread{

    //重写父类run方法,实现线程执行逻辑
    public void run() {
        System.out.println("myThead线程执行了.....");
    }
}
public class App {
    public static void main(String[] args) {
        //创建线程对象
        MyThread thread = new MyThread();
        //调用start方法启动线程
        thread.start();
        System.out.println("这是主线程.....");
    }
}
注意:

1:必须继承Thread父类
2:重写run方法,否则是空实现
3:启动线程是使用start() 方法而不是run(), 如果thread.run(), 仅仅表示对象调用方法,并不是多线程操作。

二、通过实现Runable接口创建线程
//方式2:通过实行Runnable接口创建线程
public class MyRunnable implements Runnable {
    //线程执行逻辑
    public void run() {
        System.out.println("线程:" + Thread.currentThread().getName()+" 执行了...");
    }
}
public class App {

    public static void main(String[] args) {

        //创建接口实现类
        MyRunnable run = new MyRunnable();

        //参数1:Runnable接口实例
        //参数2:线程名字
        Thread thread = new Thread(run, "线程1");

        //调用start方法启动线程
        thread.start();
        System.out.println("这是主线程.....");
    }
}
注意

1: 必须要实现Runnable接口,重写run方法
2: 线程运行需要借助Thread(Runnable run, String ThreadName) 构造器
3: 如果线程不需要多次运行,可以使用匿名内部类实现

public class App2 {

    public static void main(String[] args) {
        //参数1:Runnable接口实例
        //参数2:线程名字
        Thread thread = new Thread(new Runnable(){
            //线程执行逻辑
            public void run() {
                System.out.println("线程:" + Thread.currentThread().getName()+" 执行了...");
            }
        }, "线程1");

        //调用start方法启动线程
        thread.start();
        System.out.println("这是主线程.....");
    }
}
分析

其实方式1是方式2的一种变种,本质上还是方式2, 可以打开Thread源码, 发现Thread类也实现了Runnable接口

public class Thread implements Runnable {...}

当new Thread(Runnable, threadName) 时,底层做了这个操作

public Thread(Runnable target, String name) {
        init(null, target, name, 0);
}
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
....
//在Thread类中也维护了一个Runnable 对象
this.target = target;
....
}

当thread调用start方法时,实际上调用本地方法,让操作系统开辟线程

public synchronized void start() {
....
    start0();
....
}
private native void start0();

最后回调执行run方法,就发现底层实际上是执行Runnable方法

    public void run() {
        if (target != null) {
            target.run();
        }
    }
三、通过 Callable 和 FutureTask 创建线程
//方式3:使用Callable跟FuntrueTask方式实现
public class MyCallable implements Callable<String> {
    //线程执行逻辑
    public String call() throws Exception {
        System.out.println("线程:" + Thread.currentThread().getName() + ",执行....");
        //5s之后返回结果
        Thread.sleep(5000);
        return "线程执行完后的返回值";
    }
}
public class App3 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        //创建Callable接口实现类对象
        MyCallable callable = new MyCallable();

        //创建FutureTask对象
        FutureTask ft = new FutureTask(callable);

        //将FutureTask对象对象作为参数传入
        Thread thread = new Thread(ft, "线程1");

        //调用start方法启动线程
        thread.start();

        //获取线程执行完毕返回的值
        System.out.println(ft.get());
        System.out.println("这是主线程.....");
    }
}
注意

1: 必须要Callable 接口,重写call方法, 接口泛型机试call方法返回值类型
2: 需要与FutrueTask对象协同使用
3: FutrueTask 使用可以与Thread对象协同使用,也可以跟线程池配合使用
4: 可以调用FutrueTask对象中的get方法获取线程执行返回值

分析

方式3跟方式1,方式2最大区别在于方式3线程执行完之后可以通过回到方法get返回线程执行结果。那么,它是怎么做的的呢。
方式3原理
看上图,FutrueTask类实现了Runnable接口,该类也持有一个Callable的属性,目的用于接收传入的Callable对象。同时重写了run方法,在run方法中调用了callable接口的call方法,得到返回,而call方法,就是线程逻辑执行方法。此时定义一个变量outcome保存执行结果,那么完全可以通过get方法线程执行结果。再看会操作代码


    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建Callable接口实现类对象
        MyCallable callable = new MyCallable();
        //创建FutureTask对象
        FutureTask ft = new FutureTask(callable);
        //将FutureTask对象对象作为参数传入
        Thread thread = new Thread(ft, "线程1");
        //调用start方法启动线程
        thread.start();
        //获取线程执行完毕返回的值
        System.out.println(ft.get());
        System.out.println("这是主线程.....");
    }

得到结果,其实方法3也是方式2的一种变种。本质还是一样的。

总结:

1: 如果线程类已经继承了其他类,那么创建线程就需要使用Runnable接口/FutureTask方式
2: 如果需要返回值则需要FutrueTask方式

3: 如果需要使用线程池,可以使用Runnable跟FutrueTask方式。


猜你喜欢

转载自blog.csdn.net/wolfcode_cn/article/details/80900837