Saul's blog Saul's blog
首页
后端
分布式
前端
更多
分类
标签
归档
友情链接
关于
GitHub (opens new window)

Saul.J.Wu

立身之本,不在高低。
首页
后端
分布式
前端
更多
分类
标签
归档
友情链接
关于
GitHub (opens new window)
  • Java入门基础

  • Java核心基础

    • 多线程

      • 基本概念
      • 线程的创建和使用
        • Thread类
        • 实现方式
        • 实现方式一
          • API
        • 实现方式二
          • 例子
          • API文档
        • 比较线程两种方式
        • Thread类的常用方法
          • 线程名相关
          • yield
          • join
          • sleep
          • isAlive
        • 线程的调度
        • 线程优先级
      • 线程的生命周期
      • 线程的同步
      • 线程的通信
      • Callable和线程池
    • Java常用类

    • 枚举类与注解

    • Java集合

    • 数据结构与算法

    • 泛型

    • IO流

    • 网络编程

    • 反射

    • 函数式编程

  • 设计模式

  • Web开发

  • SpringBoot

  • 微服务

  • Elasticsearch

  • 运维

  • 后端
  • Java核心基础
  • 多线程
SaulJWu
2020-12-17

线程的创建和使用

这是多线程吗?

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!");
    }
}
1
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类

  1. 定义子类继承Thread类。
  2. 子类中重写Thread类中的run()方法.
  3. 创建Thread子类对象,即创建了线程对象。
  4. 调用线程对象start方法:自动启动线程,调用run方法。

方式二:实现Runnable接口

  1. 定义子类,实现Runnable接口。
  2. 子类中重写Runnable接口中的run方法。
  3. 通过Thread类含参构造器创建线程对象。
  4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
  5. 调用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 the run method of class Thread. 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:

翻译过来是:

  1. 定义子类继承Thread类。
  2. 子类中重写Thread类中的run()方法.
  3. 创建Thread子类对象,即创建了线程对象。
  4. 调用线程对象start方法:启动线程,调用run方法。
class PrimeThread extends Thread {
         long minPrime;
         PrimeThread(long minPrime) {
             this.minPrime = minPrime;
         }

         public void run() {
             // compute primes larger than minPrime
              . . .
         }
}
1
2
3
4
5
6
7
8
9
10
11

start方法启动当前线程,并调用当前线程的run()

image-20201217173122670

比如:遍历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);
            }
        }
    }
}
1
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);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

输出结果:

image-20201217173900008

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);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

image-20201217174405724

可以看到,只有一个线程,而且是顺序执行,只是普通方法,没有启动多线程模式。

而且idea已经告诉你了:

image-20201217174603952

问题二:试试多次调用同一个线程对象

@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);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

结果:

image-20201217174752036

第二次启动线程时,报java.lang.IllegalThreadStateException错,不能重复调用。

我们来点击一下start方法,看看源码:

image-20201217174934212

已经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);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

运行结果:

image-20201217175221321

要想调用多线程,只能创建多个线程对象。

优化代码,使用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();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

这样虽然可以启动2个线程,但是没有达到并行效果:

image-20201217184742972

一个线程执行完毕后,才执行第二个线程,这样达不到我们预期的效果,

事实上,还可以定义一个内部类,让重复代码减少,也能实现并行效果:

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

image-20201217184851323

这样就满足我们的并行效果了

总结:

  • 如果自己手动调用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();
    }
}
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
窗口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
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
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 the run method. An instance of the class can then be allocated, passed as an argument when creating Thread, 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
        . . .
    }
}
1
2
3
4
5
6
7
8
9
10
11
  1. 定义子类,实现Runnable接口。
  2. 子类中重写Runnable接口中的run方法。
  3. 通过Thread类含参构造器创建线程对象。
  4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
  5. 调用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();
    }
}
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

思考:为什么调用的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();
    }
}
1
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();
    }
}
1
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
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
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();
    }
}
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
窗口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
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
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 {}
1

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);
}
1
2
3
/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}
1
2
3
4
5

所以这里我们就知道它的默认命名规则了,threadInitNumber是全局常量,默认值是0

而且我还发现,main方法的threadInitNumber是从0开始,单元测试的threadInitNumber是从1开始。

事实上,它还有一个带命名的一个构造器:

public Thread(String name) {
    init(null, null, name, 0);
}
1
2
3

主线程其实也可以改变名字

Thread.currentThread().setName("test主线程");
1

完整代码:

@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);
        }
    }
}
1
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);
        }
    }
}
1
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();
            }
        }
    }
}
1
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();
            }
        }
    }
}
1
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);
            }
        }
    }
}
1
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);
        }
    }
}
1
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);
        }
    }
}
1
2
3
4
5
6
7
8

这次可以看到子线程已经立刻完成了,而主线程还在等一秒执行一次。

套路?网络图

image-20201217190940145

# 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());
}
1
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:10
    • MIN _PRIORITY:1
    • NORM_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();
}
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

输出结果:

image-20201217194714331

从多次执行单元测试结果,可以看出,低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

帮我改善此页面 (opens new window)
#Thread类
上次更新: 2020/12/20, 05:47:54
基本概念
线程的生命周期

← 基本概念 线程的生命周期→

最近更新
01
zabbix学习笔记二
02-28
02
zabbix学习笔记一
02-10
03
Linux访问不了github
12-08
更多文章>
Theme by Vdoing | Copyright © 2020-2022 Saul.J.Wu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式