线程的创建和使用
这是多线程吗?
public class Sample{
public void method1(String str){
System.out.println(str);
}
public void method2(String str){
method1(str);
}
public static void main(String[] args){
Sample s = new Sample();
s.method2("hello!");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
不是多线程,这个案例一条线程就可以完成了。
# Thread类
Java
语言的JVM
允许程序运行多个线程,它通过java.lang.Thread
类来体现。Thread
类的特性- 每个线程都是通过某个特定
Thread
对象的run()
方法来完成操作的,经常把run()
方法的主体称为线程体。 - 通过该
Thread
对象的start()
方法来启动这个线程,而非直接调用run()
。
- 每个线程都是通过某个特定
构造器
Thread()
:创建新的Thread对象。Thread(String threadname)
:创建线程并指定线程实例名。Thread(Runnable target)
:指定创建线程的目标对象,它实现了Runnable
接口中的run()
方法。Thread(Runnable target, String name)
:创建新的Thread
对象。
# 实现方式
API中创建线程的两种方式
JDK1.5之前创建新执行线程有两种方法:
- 1、继承
Thread
类的方式 - 2、实现
Runnable
接口的方式
方式一:继承
Thread
类
- 定义子类继承
Thread
类。 - 子类中重写
Thread
类中的run()
方法. - 创建
Thread
子类对象,即创建了线程对象。 - 调用线程对象
start
方法:自动启动线程,调用run
方法。
方式二:实现
Runnable
接口
- 定义子类,实现
Runnable
接口。 - 子类中重写
Runnable
接口中的run
方法。 - 通过
Thread
类含参构造器创建线程对象。 - 将
Runnable
接口的子类对象作为实际参数传递给Thread
类的构造器中。 - 调用
Thread
类的start
方法:开启线程,自动调用Runnable
子类接口的run
方法。
# 实现方式一
继承
Thread
类
# API
Java Platform SE 8:
There are two ways to create a new thread of execution. One is to declare a class to be a subclass of
Thread
. This subclass should override therun
method of classThread
. An instance of the subclass can then be allocated and started. For example, a thread that computes primes larger than a stated value could be written as follows:
翻译过来是:
- 定义子类继承
Thread
类。 - 子类中重写
Thread
类中的run()
方法. - 创建
Thread
子类对象,即创建了线程对象。 - 调用线程对象
start
方法:启动线程,调用run
方法。
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
2
3
4
5
6
7
8
9
10
11
start方法启动当前线程,并调用当前线程的
run()
比如:遍历100以内的奇数和偶数,分开线程实现:
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName()+" --- i = " + i);
}
}
}
}
2
3
4
5
6
7
8
9
10
11
@Test
void run() {
//创建`Thread`子类对象,即创建了线程对象。
MyThread t1 = new MyThread();
//调用线程对象`start`方法:启动线程,调用`run`方法。
t1.start();
//主线程
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName()+" *** i = " + i);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
输出结果:
Thread.currentThread().getName()
获取当前线程的名字。
可以看到2个线程都是独立运行的。
问题一:如果自己调用run()方法,可以吗?
改改上面的测试案例,试试:
@Test
void run() {
//创建`Thread`子类对象,即创建了线程对象。
MyThread t1 = new MyThread();
//调用线程对象`start`方法:启动线程,调用`run`方法。
// t1.start();
t1.run();
//主线程
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName()+" *** i = " + i);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
可以看到,只有一个线程,而且是顺序执行,只是普通方法,没有启动多线程模式。
而且idea
已经告诉你了:
问题二:试试多次调用同一个线程对象
@Test
void run() {
//创建`Thread`子类对象,即创建了线程对象。
MyThread t1 = new MyThread();
//调用线程对象`start`方法:启动线程,调用`run`方法。
t1.start();
t1.start();
//主线程
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName()+" *** i = " + i);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
结果:
第二次启动线程时,报java.lang.IllegalThreadStateException
错,不能重复调用。
我们来点击一下start
方法,看看源码:
已经start
的线程,它的threadStatus
不为0,肯定会抛出java.lang.IllegalThreadStateException
异常。
那么最后我们只能重新再创建一个线程的对象,然后调用start
方法:
@Test
void run() {
//创建`Thread`子类对象,即创建了线程对象。
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
//调用线程对象`start`方法:启动线程,调用`run`方法。
t1.start();
t2.start();
//主线程
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName()+" *** i = " + i);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
运行结果:
要想调用多线程,只能创建多个线程对象。
优化代码,使用Thread类的匿名子类的方式
既然都是只用一次,那么可以作为匿名子类的方式来运行。
@Test
void TestInnerThread(){
new MyThread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName()+" *** i = " + i);
}
}
}
}.start();
new MyThread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName()+" --- i = " + i);
}
}
}
}.start();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
这样虽然可以启动2个线程,但是没有达到并行效果:
一个线程执行完毕后,才执行第二个线程,这样达不到我们预期的效果,
事实上,还可以定义一个内部类,让重复代码减少,也能实现并行效果:
class Mythread extends Thread {
private int x;
public Mythread(int x) {
this.x = x;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (this.x % 2 != 0 && i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + " --- i = " + i);
} else {
System.out.println(Thread.currentThread().getName() + " --- i = " + i);
}
}
}
}
@Test
void TestOuterThread2() {
new Mythread(1).start();
new Mythread(2).start();
}
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
这样就满足我们的并行效果了
总结:
- 如果自己手动调用
run()
方法,那么就只是普通方法,没有启动多线程模式。 run()
方法由JVM
调用,什么时候调用,执行的过程控制都有操作系统的CPU
调度决定。- 想要启动多线程,必须调用
start
方法。 - 一个线程对象只能调用一次
start()
方法启动,如果重复调用了,则将抛出以上的异常“IllegalThreadStateException
”。
# 实现方式二
方式二:实现
Runnable
接口
# 例子
先从一个例子,来引入这个实现方式,例如,创建三个买票窗口,总票数为100
class Window extends Thread {
private static int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(this.getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
//System.out.println(this.getName() + "抢不到票,卖光了!");
break;
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
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
窗口2:卖票,票号为:100
窗口1:卖票,票号为:100
窗口3:卖票,票号为:100
窗口1:卖票,票号为:98
窗口1:卖票,票号为:96
窗口1:卖票,票号为:95
窗口2:卖票,票号为:99
窗口1:卖票,票号为:94
窗口3:卖票,票号为:97
窗口1:卖票,票号为:92
窗口2:卖票,票号为:93
窗口2:卖票,票号为:89
窗口2:卖票,票号为:88
窗口2:卖票,票号为:87
窗口1:卖票,票号为:90
窗口3:卖票,票号为:91
窗口1:卖票,票号为:85
窗口1:卖票,票号为:83
窗口1:卖票,票号为:82
窗口1:卖票,票号为:81
窗口2:卖票,票号为:86
窗口1:卖票,票号为:80
窗口1:卖票,票号为:78
窗口1:卖票,票号为:77
窗口1:卖票,票号为:76
窗口1:卖票,票号为:75
窗口1:卖票,票号为:74
窗口1:卖票,票号为:73
窗口1:卖票,票号为:72
窗口1:卖票,票号为:71
窗口1:卖票,票号为:70
窗口1:卖票,票号为:69
窗口1:卖票,票号为:68
窗口1:卖票,票号为:67
窗口3:卖票,票号为:84
窗口1:卖票,票号为:66
窗口1:卖票,票号为:64
窗口1:卖票,票号为:63
窗口1:卖票,票号为:62
窗口1:卖票,票号为:61
窗口1:卖票,票号为:60
窗口1:卖票,票号为:59
窗口1:卖票,票号为:58
窗口1:卖票,票号为:57
窗口1:卖票,票号为:56
窗口1:卖票,票号为:55
窗口1:卖票,票号为:54
窗口1:卖票,票号为:53
窗口1:卖票,票号为:52
窗口1:卖票,票号为:51
窗口1:卖票,票号为:50
窗口1:卖票,票号为:49
窗口1:卖票,票号为:48
窗口1:卖票,票号为:47
窗口1:卖票,票号为:46
窗口1:卖票,票号为:45
窗口1:卖票,票号为:44
窗口1:卖票,票号为:43
窗口1:卖票,票号为:42
窗口1:卖票,票号为:41
窗口1:卖票,票号为:40
窗口1:卖票,票号为:39
窗口1:卖票,票号为:38
窗口1:卖票,票号为:37
窗口1:卖票,票号为:36
窗口2:卖票,票号为:79
窗口1:卖票,票号为:35
窗口3:卖票,票号为:65
窗口1:卖票,票号为:33
窗口1:卖票,票号为:31
窗口1:卖票,票号为:30
窗口1:卖票,票号为:29
窗口1:卖票,票号为:28
窗口1:卖票,票号为:27
窗口1:卖票,票号为:26
窗口1:卖票,票号为:25
窗口2:卖票,票号为:34
窗口2:卖票,票号为:23
窗口2:卖票,票号为:22
窗口2:卖票,票号为:21
窗口2:卖票,票号为:20
窗口2:卖票,票号为:19
窗口2:卖票,票号为:18
窗口2:卖票,票号为:17
窗口2:卖票,票号为:16
窗口2:卖票,票号为:15
窗口2:卖票,票号为:14
窗口2:卖票,票号为:13
窗口2:卖票,票号为:12
窗口2:卖票,票号为:11
窗口1:卖票,票号为:24
窗口1:卖票,票号为:9
窗口1:卖票,票号为:8
窗口3:卖票,票号为:32
窗口1:卖票,票号为:7
窗口1:卖票,票号为:5
窗口1:卖票,票号为:4
窗口2:卖票,票号为:10
窗口1:卖票,票号为:3
窗口1:卖票,票号为:1
窗口3:卖票,票号为:6
窗口2:卖票,票号为:2
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
首先第一个是线程安全问题,有重复的数字,
第二个问题是票数顺序不是依次递减,其实已经抢到票了,但是输出需要时间,所以慢了些。
但是我们的关注点不在这两点,关注的是ticket
,如果不定义为全局变量,而是普通变量,那么每次实例化一个线程的时候,都会创建独立的数据,但是我们想要的是3个对象共享100张票。
那么除了将ticket
定义为全局变量,还能怎么做到3个对象共享数据呢?
引入第二种实现多线程的方式:实现Runnable
接口。
# API文档
Java Platform SE 8这个文档比较旧了,其实不止两种方式,但是还可以看得到第二种方式。
The other way to create a thread is to declare a class that implements the
Runnable
interface. That class then implements therun
method. An instance of the class can then be allocated, passed as an argument when creatingThread
, and started. The same example in this other style looks like the following:
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
2
3
4
5
6
7
8
9
10
11
- 定义子类,实现
Runnable
接口。 - 子类中重写
Runnable
接口中的run
方法。 - 通过
Thread
类含参构造器创建线程对象。 - 将
Runnable
接口的子类对象作为实际参数传递给Thread
类的构造器中。 - 调用
Thread
类的start
方法:开启线程,自动调用Runnable
子类接口的run
方法。
来手写一下:
package com.saul;
/**
* 实现多线程的第二种方式
* 1. 定义子类,实现`Runnable`接口。
* 2. 子类中重写`Runnable`接口中的`run`方法。
* 3. 通过`Thread`类含参构造器创建线程对象。
* 4. 将`Runnable`接口的子类对象作为实际参数传递给`Thread`类的构造器中。
* 5. 调用`Thread`类的`start`方法:开启线程,调用`Runnable`子类接口的`run`方法。
* @author Saul.J.Wu
* @date 2020/12/17
*/
class MyThread2 implements Runnable{
public void run() {
//输出所有偶数
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println("i = " + i);
}
}
}
}
public class ThreadTest2{
public static void main(String[] args) {
//通过`Thread`类含参构造器创建线程对象。
MyThread2 myThread2 = new MyThread2();
//将`Runnable`接口的子类对象作为实际参数传递给`Thread`类的构造器中。
Thread t1 = new Thread(myThread2);
//调用`Thread`类的`start`方法:开启线程,调用`Runnable`子类接口的`run`方法。
t1.start();
}
}
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
思考:为什么调用的t1的start,自动调用run方法,为什么实际上是调用的是MyThread2方法?
来看看源码:
private Runnable target;
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
@Override
public void run() {
if (target != null) {
target.run();
}
}
2
3
4
5
6
7
8
9
10
11
12
当我们实例化一个Thread
类时,会私有化创建一个Runnable
类型的target
,当调用Thread
类的run方法时,直接调用实现Runnable
的run
方法。
再启动一个线程,应该怎么写?
class MyThread2 implements Runnable {
public void run() {
//输出所有偶数
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest2 {
public static void main(String[] args) {
//通过`Thread`类含参构造器创建线程对象。
MyThread2 myThread2 = new MyThread2();
//将`Runnable`接口的子类对象作为实际参数传递给`Thread`类的构造器中。
Thread t1 = new Thread(myThread2);
Thread t2 = new Thread(myThread2);
//调用`Thread`类的`start`方法:开启线程,调用`Runnable`子类接口的`run`方法。
t1.start();
t2.start();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
输出结果:
Thread-0:0
Thread-0:2
Thread-0:4
Thread-1:0
Thread-0:6
Thread-1:2
Thread-0:8
Thread-1:4
Thread-0:10
Thread-1:6
Thread-0:12
Thread-1:8
Thread-0:14
Thread-1:10
Thread-0:16
Thread-1:12
Thread-0:18
Thread-1:14
Thread-0:20
Thread-1:16
Thread-0:22
Thread-0:24
Thread-1:18
Thread-0:26
Thread-1:20
Thread-0:28
Thread-1:22
Thread-1:24
Thread-1:26
Thread-0:30
Thread-1:28
Thread-1:30
Thread-1:32
Thread-1:34
Thread-1:36
Thread-1:38
Thread-0:32
Thread-1:40
Thread-1:42
Thread-1:44
Thread-1:46
Thread-1:48
Thread-1:50
Thread-1:52
Thread-0:34
Thread-1:54
Thread-1:56
Thread-1:58
Thread-1:60
Thread-1:62
Thread-1:64
Thread-1:66
Thread-1:68
Thread-1:70
Thread-1:72
Thread-1:74
Thread-1:76
Thread-1:78
Thread-1:80
Thread-1:82
Thread-1:84
Thread-1:86
Thread-1:88
Thread-1:90
Thread-1:92
Thread-1:94
Thread-1:96
Thread-1:98
Thread-0:36
Thread-0:38
Thread-0:40
Thread-0:42
Thread-0:44
Thread-0:46
Thread-0:48
Thread-0:50
Thread-0:52
Thread-0:54
Thread-0:56
Thread-0:58
Thread-0:60
Thread-0:62
Thread-0:64
Thread-0:66
Thread-0:68
Thread-0:70
Thread-0:72
Thread-0:74
Thread-0:76
Thread-0:78
Thread-0:80
Thread-0:82
Thread-0:84
Thread-0:86
Thread-0:88
Thread-0:90
Thread-0:92
Thread-0:94
Thread-0:96
Thread-0:98
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
可以看到,第二种方式也可以实现并行方式操作。
回到最初,买票问题,可以不加static
class Window2 implements Runnable {
private int ticket = 100;
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
// System.out.println(Thread.currentThread().getName() + "抢不到票,卖光了!");
break;
}
}
}
}
public class WindowTest2 {
public static void main(String[] args) {
Window2 w1 = new Window2();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
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
窗口1:卖票,票号为:100
窗口3:卖票,票号为:100
窗口2:卖票,票号为:100
窗口3:卖票,票号为:98
窗口1:卖票,票号为:99
窗口3:卖票,票号为:96
窗口2:卖票,票号为:97
窗口3:卖票,票号为:94
窗口3:卖票,票号为:92
窗口1:卖票,票号为:95
窗口3:卖票,票号为:91
窗口2:卖票,票号为:93
窗口3:卖票,票号为:89
窗口1:卖票,票号为:90
窗口3:卖票,票号为:87
窗口2:卖票,票号为:88
窗口3:卖票,票号为:85
窗口3:卖票,票号为:83
窗口1:卖票,票号为:86
窗口1:卖票,票号为:81
窗口1:卖票,票号为:80
窗口3:卖票,票号为:82
窗口2:卖票,票号为:84
窗口3:卖票,票号为:78
窗口1:卖票,票号为:79
窗口3:卖票,票号为:76
窗口2:卖票,票号为:77
窗口3:卖票,票号为:74
窗口1:卖票,票号为:75
窗口1:卖票,票号为:71
窗口1:卖票,票号为:70
窗口1:卖票,票号为:69
窗口1:卖票,票号为:68
窗口1:卖票,票号为:67
窗口1:卖票,票号为:66
窗口1:卖票,票号为:65
窗口1:卖票,票号为:64
窗口1:卖票,票号为:63
窗口1:卖票,票号为:62
窗口3:卖票,票号为:72
窗口2:卖票,票号为:73
窗口3:卖票,票号为:60
窗口3:卖票,票号为:58
窗口3:卖票,票号为:57
窗口3:卖票,票号为:56
窗口3:卖票,票号为:55
窗口3:卖票,票号为:54
窗口3:卖票,票号为:53
窗口3:卖票,票号为:52
窗口3:卖票,票号为:51
窗口3:卖票,票号为:50
窗口3:卖票,票号为:49
窗口3:卖票,票号为:48
窗口3:卖票,票号为:47
窗口3:卖票,票号为:46
窗口3:卖票,票号为:45
窗口3:卖票,票号为:44
窗口3:卖票,票号为:43
窗口3:卖票,票号为:42
窗口3:卖票,票号为:41
窗口3:卖票,票号为:40
窗口3:卖票,票号为:39
窗口3:卖票,票号为:38
窗口3:卖票,票号为:37
窗口3:卖票,票号为:36
窗口3:卖票,票号为:35
窗口3:卖票,票号为:34
窗口3:卖票,票号为:33
窗口3:卖票,票号为:32
窗口1:卖票,票号为:61
窗口3:卖票,票号为:31
窗口3:卖票,票号为:29
窗口3:卖票,票号为:28
窗口3:卖票,票号为:27
窗口3:卖票,票号为:26
窗口3:卖票,票号为:25
窗口3:卖票,票号为:24
窗口3:卖票,票号为:23
窗口3:卖票,票号为:22
窗口3:卖票,票号为:21
窗口3:卖票,票号为:20
窗口3:卖票,票号为:19
窗口3:卖票,票号为:18
窗口3:卖票,票号为:17
窗口2:卖票,票号为:59
窗口3:卖票,票号为:16
窗口1:卖票,票号为:30
窗口1:卖票,票号为:13
窗口1:卖票,票号为:12
窗口1:卖票,票号为:11
窗口1:卖票,票号为:10
窗口1:卖票,票号为:9
窗口3:卖票,票号为:14
窗口3:卖票,票号为:7
窗口3:卖票,票号为:6
窗口3:卖票,票号为:5
窗口3:卖票,票号为:4
窗口3:卖票,票号为:3
窗口3:卖票,票号为:2
窗口3:卖票,票号为:1
窗口2:卖票,票号为:15
窗口1:卖票,票号为:8
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
仍然是存在线程安全问题,也不是递减
不过整体来讲,我们想要关注的点是不用加static
,为什么?
因为只造了一个对象,用这个对象启动3个线程买票,所以就不用全局变量。
# 比较线程两种方式
继承方式和实现方式的联系与区别
public class Thread extends Objectimplements Runnable
- 区别
- 继承
Thread
:线程代码存放Thread
子类run
方法中。 - 实现
Runnable
:线程代码存在接口的子类的run
方法。
- 继承
- 实现方式的好处
- 避免了单继承的局限性
- 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
所以开发中优先选择实现Runnable
接口的方式
那继承和实现两种方式之间有什么联系?
public class Thread implements Runnable {}
Thread
类本身也是实现Runnable
,并且重写了run
方法。
那继承和实现两种方式之间的相同点是都需要重写run()
方法,将线程要执行的逻辑代码声明在run()
中。
# Thread类的常用方法
void start()
: 启动线程,并执行对象的run()方法run()
: 线程在被调度时执行的操作String getName()
: 返回线程的名称void setName(String name)
:设置该线程名称static Thread currentThread()
: 返回当前线程。在Thread
子类中就是this
,通常用于主线程和Runnable
实现类yield()
:线程让步,释放当前cpu的执行权join():
当某个程序执行流中调用其他线程的join() 方法时,当前线程将被阻塞,直到join() 方法加入的join 线程执行完为止sleep(long millitime)
:线程休眠/睡眠,实际上就是阻塞。stop()
:强制线程生命期结束,不推荐使用(已过时)isAlive()
:返回boolean
,判断线程是否还活着
# 线程名相关
关于Thread类的命名
我们来看看源码:
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
2
3
/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
2
3
4
5
所以这里我们就知道它的默认命名规则了,threadInitNumber
是全局常量,默认值是0
而且我还发现,main方法的threadInitNumber
是从0开始,单元测试的threadInitNumber
是从1开始。
事实上,它还有一个带命名的一个构造器:
public Thread(String name) {
init(null, null, name, 0);
}
2
3
主线程其实也可以改变名字
Thread.currentThread().setName("test主线程");
完整代码:
@Test
void run() {
//创建`Thread`子类对象,即创建了线程对象。
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
//调用线程对象`start`方法:启动线程,调用`run`方法。
t1.start();
t2.start();
//主线程
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
Thread.currentThread().setName("test主线程");
System.out.println(Thread.currentThread().getName() + " *** i = " + i);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static Thread currentThread()
: 返回当前线程,返回了当前线程的对象,那么我们就可以操作它了。
# yield
yield()
:线程让步,释放当前cpu的执行权暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
若队列中没有同优先级的线程,忽略此方法
@Test
void run() {
//创建`Thread`子类对象,即创建了线程对象。
MyThread t1 = new MyThread();
//调用线程对象`start`方法:启动线程,调用`run`方法。
t1.start();
//主线程
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
Thread.currentThread().setName("test主线程");
System.out.println(Thread.currentThread().getName() + " *** i = " + i);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName()+" --- i = " + i);
}
if (i % 20 == 0) {
this.yield();
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
一旦线程执行此方法,释放当前CPU的执行,虽然多次测试效果不是很理想,那是因为线程让步了,然后到下次分配,此线程又抢到了运行。
# join
join():
当某个程序执行流中调用其他线程的join() 方法时,当前线程将被阻塞,直到join() 方法加入的join 线程执行完为止低优先级的线程也可以获得执行
@Test
void run() {
//创建`Thread`子类对象,即创建了线程对象。
MyThread t1 = new MyThread();
//调用线程对象`start`方法:启动线程,调用`run`方法。
t1.start();
//主线程
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
Thread.currentThread().setName("test主线程");
System.out.println(Thread.currentThread().getName() + " *** i = " + i);
}
if (i == 20) {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(this.getName()+" --- i = " + i);
}
}
}
}
2
3
4
5
6
7
8
9
10
11
现在,主线程执行到20,就开始执行子线程,等到子线程执行完毕后,才让主线程执行。
# sleep
sleep(long millitime)
:线程休眠/睡眠,实际上就是阻塞。
令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
抛出InterruptedException
异常。
@Test
void run3() throws InterruptedException {
//创建`Thread`子类对象,即创建了线程对象。
MyThread t1 = new MyThread();
//调用线程对象`start`方法:启动线程,调用`run`方法。
t1.start();
//主线程
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
Thread.sleep(1000);
Thread.currentThread().setName("test主线程");
System.out.println(Thread.currentThread().getName() + " *** i = " + i);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(this.getName()+" --- i = " + i);
}
}
}
2
3
4
5
6
7
8
这次可以看到子线程已经立刻完成了,而主线程还在等一秒执行一次。
套路?网络图
# isAlive
isAlive()
:返回boolean
,判断线程是否还活着
@Test
void run() {
//创建`Thread`子类对象,即创建了线程对象。
MyThread t1 = new MyThread();
//调用线程对象`start`方法:启动线程,调用`run`方法。
t1.start();
//主线程
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
Thread.currentThread().setName("test主线程");
System.out.println(Thread.currentThread().getName() + " *** i = " + i);
}
if (i == 20) {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("t1.isAlive() = " + t1.isAlive());
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 线程的调度
调度策略
- 时间片:默认是此策略,执行完一个线程然后再执行另一个线程。
- 抢占式:高优先级的线程抢占CPU。
Java的调度方法
- 同优先级线程组成先进先出队列(先到先服务),使用时间片策略
- 对高优先级,使用优先调度的抢占式策略
# 线程优先级
- 线程的优先级等级
MAX_PRIORITY
:10MIN _PRIORITY
:1NORM_PRIORITY
:5,默认优先级
- 涉及的方法
getPriority()
:返回线程优先值setPriority(intnewPriority)
:改变线程的优先级
- 说明
- 线程创建时继承父线程的优先级
- 低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
先前我们有一个案例,使用Thread类的匿名子类的方式,但是它们不是并行效果,现在可以设置线程优先级,让线程并行
class Mythread extends Thread {
private int x;
public Mythread(int x) {
this.x = x;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (this.x % 2 != 0 && i % 2 != 0) {
System.out.println(getName() + ":" + getPriority() + ":" + i);
} else {
System.out.println(getName() + ":" + getPriority() + ":" + i);
}
}
}
}
@Test
void TestOuterThread2() {
Mythread t1 = new Mythread(1);
t1.setPriority(1);
t1.start();
Mythread t2 = new Mythread(2);
t2.setPriority(10);
t2.start();
}
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
输出结果:
从多次执行单元测试结果,可以看出,低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用