深度解析多线程的创建方式和正确启动多线程

深度解析多线程的创建方式和正确启动多线程

一、创建多线程

1. 实现多线程

java 实现多线程的方式准确来说有两种(oracle官方文档说的):

(1)实现 Runnable 接口, 重写run()函数,运行start()方法

代码演示:

/**

* 用Runnable方式创建线程

*/

public class RunnableStyle implements Runnable {

@Override

public void run() {

System.out.println("我是一个Runnable");

}

public static void main(String[] args) {

new Thread(new RunnableStyle()).start();

}

}

(2)继承 Thread 类,重写run()函数,运行start()方法

/**

* 用Thread方式创建线程

* 每个线程只能操作当前线程类的对象变量,耦合性太强

*/

public class ThreadStyle extends Thread {

@Override

public void run() {

System.out.println("我是一个 Thread");

}

public static void main(String[] args) {

new ThreadStyle().start();

}

}

2. 两种实现多线程方法的对比

方法1(实现Runnable接口)更好的三个优势:

(1)解耦性好,run方法业务与线程建立逻辑分离

表现1,这样创建不同的线程可以共享Runnable实例中的变量和方法,即多个线程可以操作同一资源,而Thread只能操作当前对象的业务,即每新建一个线程类,当前资源类变量都会初始化

代码实现区别

实现Runnable接口

/**

* 用Runnable方式创建线程

* 解耦,不同线程可以操作同一资源对象的变量,run业务与线程建立分离

*/

public class RunnableStyle implements Runnable {

private int count = 10;

@Override

public void run() {

count--;

System.out.println(Thread.currentThread().getName() + " : count=" + count);

}

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

RunnableStyle runnableStyle = new RunnableStyle();

new Thread(runnableStyle,"Runnable1").start();

Thread.sleep(1000);

new Thread(runnableStyle, "Runnable2").start();

}

}

Runnable1 : count=9

Runnable2 : count=8

继承Thread类

/**

* 用Thread方式创建线程

* 每个线程只能操共享当前线程类的对象变量,耦合性太强

*/

public class ThreadStyle extends Thread {

private int count = 10;

@Override

public void run() {

count--;

System.out.println(Thread.currentThread().getName() + " : count=" + count);

}

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

ThreadStyle thread1 = new ThreadStyle();

thread1.setName("Thread1");

thread1.start();

Thread.sleep(1000);

ThreadStyle thread2 = new ThreadStyle();

thread2.setName("Thread2");

thread2.start();

}

}

Thread1 : count=9

Thread2 : count=9

#### (2)节约资源,每次新建任务无需每次都新建线程,例如线程池类

使用继承Thread的方式的话,那么每次想新建一个任务,只能新建一个独立的线程,而这样做的损耗会比较大(比如重头开始创建一个线程、执行完毕以后再销毁等。如果线程的实际工作内容,也就是run()函数里只是简单的打印一行文字的话,那么可能线程的实际工作内容还不如损耗来的大)。如果使用Runnable的方式,只需要更换Runnable实例,不需要新建Thread类,就可以实现线程的重复利用,线程池就是基于这个原理,从而大大减小损耗。

#### (3)可扩展性好,可用实现多继承接口,不能实现多继承类

继承Thread类以后,由于Java语言不支持双继承,这样就无法再继承其他的类,限制了可扩展性。

### 3. 两种方法的本质对比

方法一和方法二,也就是“实现Runnable接口并传入Thread类”和“继承Thread类然后重写run()”在实现多线程的本质上,并没有区别,都是最终调用了start()方法来新建线程。这两个方法的最主要区别在于run()方法的内容来源:

```java

...

public class Thread implements Runnable {

private Runnable target;

...

@Override

public void run() {

if (target != null) {

target.run();

}

}

...

}

方法一:最终调用Runnable实例的run()方法(target.run());

方法二:直接将Thread类的里整个 run() 方法重写

4. 创建线程时同时使用Runnable、Thread两种方法会发生什么?

如果 同时使用Runnable、Thread两种方法,那么只会执行 Thread 中重写的 run()方法,因为将父类中的run方法覆盖了:

...

public class Thread implements Runnable {

private Runnable target;

...

// 该 run 方法直接被重写了,便不再会执行 target.run()方法了

@Override

public void run() {

if (target != null) {

target.run();

}

}

...

}

同时执行两种方式的测试:

/**

* 同时执行两种方式的测试

* 匿名内部类

*/

public class DoubleStyle {

public static void main(String[] args) {

new Thread(() -> {

System.out.println("我是 Runnable");

}) {

@Override

public void run() {

System.out.println("我是 Thread");

}

}.start();

}

}

执行结果:

我是 Thread 的 run方法

准确的讲,创建线程只有一种方式那就是构造Thread类,而实现线程的执行单元有两种方式:一是实现Runnable接口的run方法,并把Runnable实例传给Thread类,二是重写Thread的run方法(继承Thread类)

5. 其它创建线程的方式

其它创建线程的方式也有很多,但都是在代码的写法上的千变万化,本质上还是都基于 Thread 类和 Runnable 类的重写run()方法。比如以下几种方式:

(1)线程池创建线程

线程池本质上,是创建有限个线程排队处理多于线程数量的业务,为了节省创建和销毁线程的时间

本质上还是实现Runnable接口创建线程类

源码片段:

(2)通过Callable和FutureTask创建线程

Callable和FutureTask类同时继承了Future接口和Runnnable接口,本质还是实现Runnnable接口中的run方法

(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);

}

}

其中 TimerTask 又是实现了 Runnable 接口 的类。

所以,本质上,创建线程只有一种方式那就是构造Thread类,而实现线程的执行单元有两种方式:一是实现Runnable接口的run方法,并把Runnable实例传给Thread类,二是重写Thread的run方法(继承Thread类)。

二、启动多线程

1. start()和run()对比

代码测试:

public class StartAndRunMethod {

public static void main(String[] args) {

Runnable runnable = () -> {

System.out.println(Thread.currentThread().getName());

};

runnable.run();

new Thread(runnable).start();

}

}

测试结果:

main

Thread-0

可见 run() 只是在主线程中调用了 run 方法,没有启动子线程,这显然不符合我们开启新线程的预期;而 start() 启动了一个子线程,并调用了线程中的run方法。

下面就这两种方法进行解析:

2. start()方法

执行start方法是请求 JVM 开辟一个子线程,但线程具体什么时候开始执行,取决于CPU的调度,有可能立即执行,也有可能因为繁忙稍后执行或不执行。

start方法的执行流程:

检查线程状态,只有NEW状态下的线程才能继续,否则会抛出IllegalThreadStateException(在运行中或者已结束的线程,都不能再次启动)

加入线程组

调用 start0() 方法启动线程,这是一个 native 方法,用 c++ 写的。

注意点:

start方法是被synchronized修饰的方法,可以保证线程安全;

start()方法不能执行两次,因为会有“检查线程状态”这一步,见上图;

由JVM创建的main方法线程和system组线程,并不会通过start来启动。

3. run()方法

源码:

run()方法重写的两种方式:

实现Runnable接口,重写Runnable接口的run()方法,注入到Thread类,执行target.run()。

继承Thread类,重写Thread类中的run(),这时原run()中的if(target != null){ target.run(); },这部分就会被覆盖重写。

文章来源:深度解析多线程的创建方式和正确启动多线程

微信公众号名称:Java知者

微信公众号id:JavaZhiZhe

欢迎关注,谢谢!

相关推荐

寻仙手游cdk兑换在哪? CDKEY礼包怎么兑换?
365bet下载

寻仙手游cdk兑换在哪? CDKEY礼包怎么兑换?

📅 01-26 👁️ 2175
WPS文档怎么翻页设置:全面指南
beat365app登录入口

WPS文档怎么翻页设置:全面指南

📅 02-19 👁️ 2789
App应用软件备案所需材料与完整流程
beat365app登录入口

App应用软件备案所需材料与完整流程

📅 01-05 👁️ 2333