kim.zhang

风在前,无惧!


  • 首页

  • 标签42

  • 分类12

  • 归档94

  • 搜索

创建线程的方式.md

发表于 2020-08-10 更新于 2021-11-21 分类于 并发
本文字数: 6.9k 阅读时长 ≈ 6 分钟

方式一:继承Thread类创建线程

在举栗子之前,我们先定义一个Account实体类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class Account {

private Integer accountId; // 账户id
private String accountName; // 账户名称
private Boolean vaild; // 账户状态是否可用

public Account() {

}

public Account(Integer accountId, String accountName, Boolean vaild) {
this.accountId = accountId;
this.accountName = accountName;
this.vaild = vaild;
}

@Override
public String toString() {
return "Account{" +
"accountId=" + accountId +
", accountName='" + accountName + '\'' +
", vaild=" + vaild +
'}';
}

public Integer getAccountId() {
return accountId;
}

public void setAccountId(Integer accountId) {
this.accountId = accountId;
}

public String getAccountName() {
return accountName;
}

public void setAccountName(String accountName) {
this.accountName = accountName;
}

public Boolean getVaild() {
return vaild;
}

public void setVaild(Boolean vaild) {
this.vaild = vaild;
}
}

使用继承Thread类的方法创建线程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class CreateThreadByExtend extends Thread {

private Account account;

public void setAccount(Account account) {
this.account = account;
}

public CreateThreadByExtend() {
}

public CreateThreadByExtend(Account account) {
this.account = account;
}

@Override
public void run() {
System.out.println("正在执行run方法时,thread的状态:" + this.getName() + ":" + this.getState());
System.out.println("正在执行run方法时,account information:" + this.account);
}

public static void main(String[] args) {
Account acc = new Account(1, "account1", true);
CreateThreadByExtend thread1 = new CreateThreadByExtend();
thread1.setAccount(acc);
System.out.println("thread对象已经创建,但是还没有start时,线程的状态:" + thread1.getName() + ":" + thread1.getState());
thread1.start();

// 确保线程已经执行完毕
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("thread已经运行完毕后,线程的状态:" + thread1.getName() + ":" + thread1.getState());
System.out.println("thread执行完毕后,account information:" + acc);

System.out.println("*************************");
Thread thread2 = new CreateThreadByExtend(acc);
thread2.start();
}
}

运行结果:

1
2
3
4
5
6
7
8
thread对象已经创建,但是还没有start时,线程的状态:Thread-0:NEW
正在执行run方法时,thread的状态:Thread-0:RUNNABLE
正在执行run方法时,account information:Account{accountId=1, accountName='account1', vaild=true}
thread已经运行完毕后,线程的状态:Thread-0:TERMINATED
thread执行完毕后,account information:Account{accountId=1, accountName='account1', vaild=true}
*************************
正在执行run方法时,thread的状态:Thread-1:RUNNABLE
正在执行run方法时,account information:Account{accountId=1, accountName='account1', vaild=true}

需要注意以下几个问题:

1.变量可以通过什么方式传递给线程?

​ 可以通过setter方法,或者带参构造函数。

2.线程的状态?

​ 在new Thread,但未start之前,线程的状态是NEW。在start线程后,线程的状态是RUNNABLE。在线程运行完成后,线程的状态是TERMINATED。

3.使用继承Thread的方式创建线程有什么优点、缺点?

* 优点:可以使用Thread类已经有的方法,可以使用this去调用
* 缺点:Java的单继承机制使得只能继承Thread一个类

方式二:使用Runnable创建线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class CreateThreadByImplement implements Runnable {

private Account account;

public CreateThreadByImplement() {
}

public CreateThreadByImplement(Account account) {
this.account = account;
}

public void setAccount(Account account) {
this.account = account;
}

@Override
public void run() {
System.out.println("正在执行run方法时,thread的状态:" + Thread.currentThread() + ":" + Thread.currentThread().getState());
System.out.println("正在执行run方法时,account information:" + this.account);
}

public static void main(String[] args) {
Account acc = new Account(1, "account1", true);
CreateThreadByImplement runnable = new CreateThreadByImplement();
runnable.setAccount(acc);
Thread thread1 = new Thread(runnable);

System.out.println("thread对象已经创建,但是还没有start时,线程的状态:" + thread1.getName() + ":" + thread1.getState());
thread1.start();

// 确保线程已经执行完毕
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("thread已经运行完毕后,线程的状态:" + thread1.getName() + ":" + thread1.getState());
System.out.println("thread执行完毕后,account information:" + acc);

System.out.println("*************************");
Thread thread2 = new Thread(new CreateThreadByImplement(acc));
thread2.start();
}
  • 实现Runnable接口的类需要借助Thread类来启动线程,因为Runnable接口中没有start()方法。

  • 不能使用this关键字了,需要使用Thread的静态方法来获取线程信息等。

  • 使用Runnable接口实现可以继承其他类且可以继承多个接口,缺点是在run方法中不可以用this直接使用Thread的方法,需要使用Thread.currentThread先获取到线程对象


方式三:使用Callable创建线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class CreateThreadByCallable implements Callable<Account> {

private Account account;

public CreateThreadByCallable() {
}

public CreateThreadByCallable(Account account) {
this.account = account;
}

public void setAccount(Account account) {
this.account = account;
}

@Override
public Account call() {
System.out.println("正在执行run方法时,account information:" + this.account);
this.account.setVaild(false);
return account;
}

public static void main(String[] args) {
Account acc = new Account(1, "account1", true);
CreateThreadByCallable callable = new CreateThreadByCallable(acc);
callable.setAccount(acc);
FutureTask<Account> futureTask = new FutureTask<Account>(callable);
Thread thread1 = new Thread(futureTask);
thread1.start();

// 确保线程已经执行完毕
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("在call方法中修改Account信息后,account information:" + acc);

}
}

Callable接收一个泛型参数,这个参数影响call方法的返回值以及FutureTask的类型值。

使用Callable接口创建线程,

  • 优点:线程可以有返回值
  • 缺点:需要使用FutureTask封装一层,再传递给Thread

对比:执行run方法的本质

我们来看一下继承Thread类的run方法是怎么实现的?

1
2
3
4
5
6
7
8
9
/* What will be run. */
private Runnable target;

@Override
public void run() {
if (target != null) {
target.run();
}
}

Thread类的run方法实际上是调用Runnable接口的run方法。但如果是继承Thread类来实现线程,那就重写了Thread类的run方法,启动线程实际上执行的是重写后的代码。

1
2
3
4
5
6
class MyThread extend Thread {
@Override
public void run() {
// 自己编写的run方法,覆盖了Thread类原有的run方法
}
}

验证1:到底执行谁的run方法?

我们采用传入一个Runnable对象的方式创建一个线程。既实现Runnable接口的run方法,又重写Thread类的run方法,那么线程启动到底执行哪个run方法呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 使用匿名内部类的方式创建Runnable对象,如果使用Lambda表达式,不能在Lambda表达式中重写run方法
public class BothThreadAndRunnable {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我是Runnable接口的run方法");
}
}) {
@Override
public void run() {
System.out.println("我是重写Thread类的run方法");
}
};
t.start();
}
}

执行结果:

1
我是重写Thread类的run方法

从执行结果可知,我们重写的run方法覆盖了Thread类的run方法。也就是覆盖了Thread类的以下代码,使得Thread不再调用Runnable对象的run方法

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void run() {
System.out.println("我是重写Thread类的run方法");
}

// 我们重写的run方法(⬆)覆盖了Thread类的run方法(⬇)

@Override
public void run() {
if (target != null) {
target.run();
}
}

面试1:一句话总结线程的创建方式

准确地讲,创建线程只有一种方式,那就是构造Thread类。而实现线程的执行单元有两种方式:

  • 实现Runnable接口的run方法,并把Runnable实例传递给Thread类
  • 继承Thread类,重写Thread类的run方法

面试2:继承Thread类和实现Runnable接口的方法有什么优缺点?

继承Thread类

优点:

  • 可以直接使用this关键字调用Thread类当中的方法

缺点:

  • Java中不支持多继承,缺少灵活性、扩展性
  • 新建线程的损耗多,一个Thrad类就只能使用一次

实现Runnable接口

优点:

  • 灵活性、扩展性好
  • 新建线程的损耗小,实现了Runnable的对象可以被多个Thread反复使用

缺点:

  • 不能使用this关键字,需要使用Thread的静态方法
一毛也是爱~
Kim.Zhang 微信支付

微信支付

# 并发基础
生产者与消费者.md
子线程异常处理.md
  • 文章目录
  • 站点概览
Kim.Zhang

Kim.Zhang

且行且珍惜
94 日志
12 分类
42 标签
E-Mail Weibo
  1. 1. 方式一:继承Thread类创建线程
  2. 2. 方式二:使用Runnable创建线程
  3. 3. 方式三:使用Callable创建线程
  4. 4. 对比:执行run方法的本质
    1. 4.1. 验证1:到底执行谁的run方法?
  5. 5. 面试1:一句话总结线程的创建方式
  6. 6. 面试2:继承Thread类和实现Runnable接口的方法有什么优缺点?
粵ICP备19091267号 © 2019 – 2022 Kim.Zhang | 629k | 9:32
本站总访问量 4 次 | 有 309 人看我的博客啦 |
博客全站共176.7k字
载入天数...载入时分秒...
0%