# 线程基础

loading

# 一、基础概念

# 1、进程和线程

  • 进程

进程是程序运行资源分配的最小单位。进程是操作系统进行资源分配的最小单位,其中资源包括:CPU、内存空间、 磁盘IO等,同一进程中的多条线程共享该进程中的全部系统资源,而进程和进程之间是相互独立的。

  • 线程

线程是 CPU 调度的最小单位,必须依赖于进程而存在。线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的、 能独立运行的基本单位。

# 2、并行和并发

  • 并行

指应用能够同时执行不同的任务,例如:吃饭的时候可以边吃饭边打电话,这两件事情可以同时执行。强调的是同时执行。

  • 并发

指应用能够交替执行不同的任务,例如:单CPU核心下执行多线程并非是同时执行多个任务,而是计算机在以你觉察不到的速度来回切换执行多个任务而已。强调的是交替执行。

# 二、Java 中的线程

# 1、线程的启动和中止

# 启动

Java中启动线程的方式有两种:

  1. 继承 Thread 类。
  2. 实现 Runnable 接口。
public class NewThread {

    /**
     * 继承线程类
     */
    private static class UseThread extends Thread {

        @Override
        public void run() {
            super.run();
            System.out.println("I'm extended from Thread");
        }
    }

    /**
     * 实现 Runnable 接口
     */
    private static class UseRunnable implements Runnable {

        @Override
        public void run() {
            System.out.println("I implemented Runnable");
        }
    }

    public static void main(String[] args) {
        UseThread useThread = new UseThread();
        useThread.start();

        UseRunnable useRunnable = new UseRunnable();
        new Thread(useRunnable).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

执行结果:

I'm extended from Thread
I implemented Runnable
1
2
# Thread 和 Runnable 的区别

Thread 才是 Java 里对线程的唯一抽象,Runnable 只是对任务(业务逻辑) 的抽象。Thread 可以接受任意一个 Runnable 的实例并执行。

# 中止

其实一个线程想要中止,有3中情况。

  1. 线程自然中止 run() 方法执行完了或者是抛出了一个未处理的异常导致线程提前结束了。

  2. stop 调用 Thread 类的 suspend()resume() 或者 stop() 方法。但是这几个方式都是 过时 的,也就是不建议使用的方法。因为调用这几个方法后,可能会造成一些资源(比如 )不会释放而造成 死锁 的问题。

  3. 中断 调用 Thread 类的 interrupt() 方法对线程进行中断操作,实际上是改变了线程的中断标志位,然后在逻辑中进行处理看是否需要进行线程的中断。 这里关于中断有3个比较重要的方法: interrupt() :调用这个方法表对线程进行中断操作,将中断标志改为 trueisInterrupted() :获取中断标志位,判断当前线程是否被中断。 Thread.interrupted() :静态方法,也是获取中断标志位,判断当前线程是否被中断,但此方法会将中断标志位改为 false

使用 isInterrupted() 结束线程的例子:

public class EndThread {

    private static class UseThread extends Thread {

        public UseThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + "'s interrupt flag: " + isInterrupted());
            while (!isInterrupted()) {
                System.out.println(threadName + " is running");
                System.out.println(threadName + "(inner)'s interrupt flag: " + isInterrupted());
            }

            System.out.println(threadName + "'s interrupt flag: " + isInterrupted());
        }

        public static void main(String[] args) throws InterruptedException {
            Thread endThread = new UseThread("EndThread");
            endThread.start();
            Thread.sleep(20);
            // interrupt 是中断线程,其实只是设置了线程的标识位
            endThread.interrupt();
        }
    }
}
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

执行结果:

EndThread's interrupt flag: false
EndThread is running
EndThread(inner)'s interrupt flag: false
EndThread is running
EndThread(inner)'s interrupt flag: false
EndThread is running
EndThread(inner)'s interrupt flag: true
EndThread's interrupt flag: true
1
2
3
4
5
6
7
8

使用 Thread.interrupted() 结束线程的例子:

public class EndThread {

    private static class UseThread extends Thread {

        public UseThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + "'s interrupt flag: " + isInterrupted());
            while (!Thread.interrupted()) {
                System.out.println(threadName + " is running");
                System.out.println(threadName + "(inner)'s interrupt flag: " + isInterrupted());
            }

            System.out.println(threadName + "'s interrupt flag: " + isInterrupted());
        }

        public static void main(String[] args) throws InterruptedException {
            Thread endThread = new UseThread("EndThread");
            endThread.start();
            Thread.sleep(20);
            // interrupt 是中断线程,其实只是设置了线程的标识位
            endThread.interrupt();
        }
    }
}

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

执行结果:

EndThread's interrupt flag: false
EndThread is running
EndThread(inner)'s interrupt flag: false
EndThread is running
EndThread(inner)'s interrupt flag: false
EndThread's interrupt flag: false
1
2
3
4
5
6

可以看出,在调用 Thread.interrupted() 后不但中断了线程,还将中断标志位改为了 false

还有一点需要注意的是:如果一个线程处于阻塞状态(例如调用了 sleep()join()wait() 方法),则在线程检查中断标志位是如果发现中断标志位为 true ,则会在这些阻塞方法调用处抛出 InterruptedException 异常,并且在抛出异常后立即将线程的中断标志位清除,即重新设置为 false 。 看下面的例子:

public class HasInterruptException {

    private static class UseThread extends Thread {

        public UseThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            while (!isInterrupted()) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // 这里捕捉异常后,中断标识位是 false 的
                    System.out.println(threadName + "(in InterruptedException)'s interrupt flag: " + isInterrupted());
                    // 这里应当先做一些资源释放的操作,然后再调用 interrupt 方法
                    interrupt();
                    e.printStackTrace();
                }
                System.out.println(threadName + " is running");
            }
            System.out.println(threadName + "'s interrupt flag is " + isInterrupted());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread endThread = new UseThread("HasInterruptException");
        endThread.start();
        Thread.sleep(500);
        endThread.interrupt();
    }
}
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

执行结果:

HasInterruptException is running
HasInterruptException is running
HasInterruptException is running
HasInterruptException is running
HasInterruptException(in InterruptedException)'s interrupt flag: false
HasInterruptException is running
HasInterruptException's interrupt flag is true
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.doomthr.ch1.base.safeend.HasInterruptException$UseThread.run(HasInterruptException.java:23)
1
2
3
4
5
6
7
8
9
10

原本在 Thread.sleep(100) 时被 endThread.interrupt() 中断了,在捕捉到异常后 isInterrupted() 方法被重新设置成了 false ,此时必须重新再次调用 interrupt() 来中断线程。

# 三、对 Java 线程再多一点点认识

# 1、yield() 方法

使当前线程让出CPU占有权,但让出的时间是不可设定的,也不会释放锁资源。

# 2、join() 方法

把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行。 比如在线程 B 中调用了线程 A 的 Join()方法,直到线程 A 执行完毕后,才会继续 行线程 B。

例如:

package com.doomthr.ch1.base;

import com.doomthr.tool.SleepTool;

/**
 * Created with IntelliJ IDEA
 * User: Jerry
 * Date: 2019/12/30
 * Time: 16:50
 * Description: 演示 join 方法的使用
 */
public class UseJoin {

    static class Goddess implements Runnable {

        private Thread thread;

        public Goddess(Thread thread) {
            this.thread = thread;
        }

        public Goddess() {
        }

        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + " Goddess 开始排队打饭……");
            try {
                if (thread != null) {
                    thread.join();
                }
            } catch (InterruptedException e) {
            }
            // 休眠2秒
            SleepTool.second(2);
            System.out.println(threadName + " Goddess 打饭完成。");
        }
    }

    static class GoddessBoyfriend implements Runnable {

        public void run() {
            String threadName = Thread.currentThread().getName();
            // 休眠2秒
            SleepTool.second(2);
            System.out.println(threadName + " GoddessBoyfriend 开始排队打饭……");
            System.out.println(threadName + " GoddessBoyfriend 打饭完成。");
        }
    }

    public static void main(String[] args) throws Exception {
        Thread thread = Thread.currentThread();

        GoddessBoyfriend goddessBoyfriend = new GoddessBoyfriend();
        Thread goddessBoyfriendThread = new Thread(goddessBoyfriend);

        Goddess goddess = new Goddess(goddessBoyfriendThread);
        Thread goddessThread = new Thread(goddess);

        goddessThread.start();
        goddessBoyfriendThread.start();

        String threadName = thread.getName();

        System.out.println(threadName + " 开始排队打饭……");
        goddessThread.join();
        // 让主线程休眠2秒
        SleepTool.second(2);
        System.out.println(threadName + " 打饭完成。");
    }
}
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

执行结果:

main 开始排队打饭……
Thread-1 Goddess 开始排队打饭……
Thread-0 GoddessBoyfriend 开始排队打饭……
Thread-0 GoddessBoyfriend 打饭完成。
Thread-1 Goddess 
1
2
3
4
5

主线程正在打饭,结果被 Goddess 插队,紧接着又被 GoddessBoyfriend 插队,结果就是等待 GoddessBoyfriend 打饭完成后, Goddess 打饭,最后才是主线程的打饭。

# 3、线程的优先级

在Java中,通过 Thread 类的 setPriority() 方法对线程设置优先级,范围是从1~10,值越大优先级越高,所分配的时间片越多,但不能保证优先级高的线程一定优先于优先级低的线程执行,默认线程的优先级都是5。

# 4、守护线程

Daemon(守护)线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。这意味着,当一个 Java 虚拟机中不存在非 Daemon 线程的 时候,Java 虚拟机将会退出。可以通过调用 setDaemon(true) 将线程设置为 Daemon 线程。

Daemon 线程被用作完成支持性工作,但是在 Java 虚拟机退出时 Daemon 线程中的 finally 块并不一定会执行。在构建 Daemon 线程时,不能依靠 finally 块中的内容来确保执行关闭或清理资源的逻辑。

例如:

public class DaemonThread {

    private static class UseThread extends Thread {

        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            try {
                while (!isInterrupted()) {
                    System.out.println(threadName + " is running");
                }
                System.out.println(threadName + "'s interrupt flag: " + isInterrupted());
            } finally {
                // 守护线程中 finally 不一定起作用
                System.out.println("finally");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        UseThread useThread = new UseThread();
        // setPriority 是设置线程的优先级,范围是1到10,值越大优先级越高,默认是5,不一定起作用
        // useThread.setPriority(1);
        // 设置守护线程为 true 时,当主线程结束时,守护线程也结束
        useThread.setDaemon(true);
        useThread.start();
        Thread.sleep(5);
        // useThread.interrupt();
    }
}
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

执行结果:

Thread-0 is running
Thread-0 is running
Thread-0 is running
Thread-0 is running
Thread-0 is running
Thread-0 is running
Thread-0 is running
Thread-0 is running
1
2
3
4
5
6
7
8

可以看出 finally 中的代码并没有得到执行,以为主线程执行完毕就结束了,守护线程也跟着结束了。要想执行守护线程 finally 中的代码,需要手动的调用守护线程的 interrupt() 方法。


上次更新: 2020-08-21 09:02:51(10 小时前)