【Kill Thread Part.1-1】实现多线程的正确姿势

【Kill Thread Part.1】实现多线程的正确姿势

一、实现多线程的方法是1种还是2种还是4种?

1、Oraclle官网文档正确说法:2种

官方文档

  • 方法一:实现Runnable接口
  • 方法二:继承Thread类

2、实现Runnable接口

/**
 * 描述:用Runnable方式创建线程
 */
public class RunnableStyle implements Runnable{

    public static void main(String[] args) {
        Thread thread = new Thread(new RunnableStyle());
        thread.start();
    }

    @Override
    public void run(){
        System.out.println("用Runnable方法实现线程");
    }
}

3、继承Thread类

/**
 * 描述:用Thread方式实现线程
 */
public class ThreadStyle extends Thread{

    @Override
    public void run() {
        System.out.println("用Thread类实现线程");
    }

    public static void main(String[] args) {
        new ThreadStyle().start();
    }
}

4、两种方法的对比

  • 方法1(实现Runnable接口)更好
    • 使用Runnable接口后续会有工具类帮助我们管理
  • 方法2(继承Thread类)的缺点:
    • 业务逻辑,run()方法应该解耦。
    • 每次新建一个任务,都需要新建一个线程,需要去创建、执行、销毁。
    • Java是单继承的,这个类无法继承其它的类,限制了我们的扩展性。

5、源码分析

image-20220118163357358

image-20220118163345857

  • 方法一:
    • 实现了Runnable接口的类里,实现了Run方法,如果允许到run()方法,发现target不为空,那么就执行实现类的run方法。
  • 方法二:
    • 继承Thread类后,重写了run方法,就替代了上图中的run()方法,从而去执行。

二、思考题:同时用两种方法会怎么样?

1、测试代码

/**
 * 描述:同时使用两种方法实现线程的方式
 */
public class BothRunnableThread {

    public static void main(String[] args) {
        //匿名内部类
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("我来自Runnable");
            }
        }) {
            //覆盖了Thread中的run方法
            @Override
            public void run() {
                System.out.println("我来自Thread");
            }
        }.start();
    }
}

执行结果:

image-20220118164037273

2、现象解释

我们在创建Thread的时候,重写了run()方法,所以run方法就不存在target的那些代码了。

三、总结:最精确的描述

  • 通常我们可以分为两类,Oracle官方文档的描述。
  • 准确的讲,创建线程只有一种方式,那就是构造Thread类,而实现线程执行单元却有两种方式:
    • 方法一:实现Runnable接口的run方法,并把Runnable实例传给Thread类。
    • 方法二:重写Thread的run方法(继承Thread类)

四、典型错误观点分析

1、“线程池”创建线程也算是一种创建线程的方式

①测试代码

/**
 * 描述:线程池创建线程的方法
 */
public class ThreadPoll5 {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            executorService.submit(new Task(){});
        }
    }
}
class Task implements Runnable{

    @Override
    public void run() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName());
    }
}

②源码分析

在Executors源码中,创建线程也是通过new Thread去创建线程。

image-20220118165147693

2、通过Callable和FutureTask创建线程,也算是一种新建线程的方式

实现图,FutureTask也是由Runnable和Thread实现的

image-20220118165405898

image-20220118165526416

3、通过定时器创建也是一种方法

/**
 * 描述:定时器创建线程
 * 每隔一秒打印当前线程的名字
 */
public class DemoTimmerTask {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }, 1000, 1000);
    }
}

4、匿名内部类实现线程的创建也是一种方法

public class AnonymousInnerClassDemo {
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }.start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }).start();
    }
}

5、Lambda表达式方法

public class Lambda {
    public static void main(String[] args) {
        new Thread( () -> System.out.println(Thread.currentThread().getName())).start();
    }
}

五、常见的面试问题

image-20220118171720081

image-20220118171834192

posted @ 2022-01-18 17:19  DarkerG  阅读(38)  评论(0编辑  收藏  举报