2

感觉自己java基础知识学的太差,记录一下自己学习 深入浅出多线程 的笔记。

笔记连接:
https://flowus.cn/share/d4a68486-0162-4759-816e-551b275218f3
【FlowUs 息流】

1.进程和线程基本概念

进程产生的背景: 最初计算机只能输入一次指令然后执行一次,效率较低。后来有了批处理系统,用户可以将一串的指令交给计算机,由计算机依次处理。但是还是串行处理,随程序的阻塞而阻塞。

为了解决这个问题,出现了进程的概念:指正在运行的一个程序或应用程序,内存中可以存在多个进程。CPU采用时间片轮转的方式运行进程。 每个进程的时间片结束后,把CPU分配给下一个进程,假如当前进程任务没结束,则保存当前进程的信息,等待下一次分配;

然而,人们并不满足于此。如果一个进程包含多个子任务,进程也只能逐个执行,效率不高。

那么能不能让这些子任务同时执行呢?于是人们又提出了线程的概念,那么能不能让这些子任务同时执行呢?于是人们又提出了线程的概念,让一个线程执行一个子任务,这样一个进程就包含了多个线程,每个线程负责一个单独的子任务。
例如: 一个下载软件可以使用多个线程同时下载多个文件,从而大大缩短下载时间。

线程和进程:
image.png

2. Thread类和Runnable接口

自定义Thread类:

public class Demo {
    public static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("MyThread");
        }
    }

    public static void main(String[] args) {
        Thread myThread = new MyThread();
        myThread.start();
    }
}
  • start()方法后,该线程才算启动
  • 调用了start()方法后,虚拟机会创建一个线程,然后等到这个线程第一次得到时间片时再调用run()方法。
  • 第二次调用start()方法会抛出IllegalThreadStateException异常。、

Thread 类是一个 Runnable 接口的实现类。

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Runnable接口只有一个 run 方法, 并且有 @FunctionalInterface 注解。意思是:函数式接口, 接口里面只能有一个抽象方法

说明我们可以使用 Lambda 表达式

  public static void main(String[] args) {

        new Thread(new MyThread()).start();

        // Java 8 函数式编程,可以省略MyThread类
        new Thread(() -> {
            System.out.println("Java 8 匿名内部类");
        }).start();
    }

2.1 线程优先级

  • 每个Java程序都有一个默认的主线程,就是通过JVM启动的第一个线程main线程。
  • Java中线程优先级可以指定,范围是1~10。Java默认的线程优先级为5,
  • Java中的优先级来说不是特别的可靠,Java程序中对线程所设置的优先级只是给操作系统一个建议,操作系统不一定会采纳。而真正的调用顺序,是由操作系统的线程调度算法决定的

例如我们可以使用如下的代码测试一下:

    public static class T1 extends Thread {
        @Override
        public void run() {
            super.run();
            System.out.println(String.format("当前执行的线程是:%s,优先级:%d",
                    Thread.currentThread().getName(),
                    Thread.currentThread().getPriority()));
        }
    }

    @Test
    void contextLoads() throws ExecutionException, InterruptedException {
        IntStream.range(1, 10).forEach(i -> {
            Thread thread = new Thread(new T1());
            thread.setPriority(i);
            thread.start();
        });
    }

可以看到不完全按照优先级来, 但是大致差不多。
image.png

3. 线程组(ThreadGroup)

Java中用ThreadGroup来表示线程组,我们可以使用线程组对线程进行批量控制。

ThreadGroup是一个标准的向下引用的树状结构。
image.png

顶层的 ThreadGroup 被称为系统线程组(system thread group),由 JVM 运行时创建并拥有它。

除了系统线程组外,每个 ThreadGroup 对象都有一个父 ThreadGroup ,即直接包含它的那个 ThreadGroup 。

为什么这么设计呢?

节省资源:根据树形结构,可以很容易递归回收该 ThreadGroup 中的线程或线程组,避免了内存泄漏和资源浪费等问题。

确保线程安全:通过 ThreadGroup 可以限制某些线程的运行权限,比如可以设置特定的权限来防止线程意外获得系统特权,确保线程不会危及系统安全。可以通过重写 ThreadGroup 的 checkAccess 方法来实现限制某些线程的运行权限。

方便监控和调试:通过 ThreadGroup 可以方便地查看和操纵整个线程组中的所有线程,从而便于进行监控和调试。

例如通过下面的方法就可以获取当前线程组运行的所有线程:

IntStream.range(1, 10).forEach(i -> {
            Thread thread = new Thread(() -> {
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
            thread.start();
        });
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();

Thread[] threads = new Thread[threadGroup.activeCount()];

threadGroup.enumerate(threads);

结果:
image.png

4. Java线程的状态

在现在的操作系统中,线程是被视为轻量级进程的,所以操作系统线程的状态其实和操作系统进程的状态比较相似的。

image.png

4.1 Java线程的6个状态

// Thread.State 源码
public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

4.1.1 NEW

文档中含义为: Thread state for a thread which has not yet started. 表示尚未启动的线程

Thread thread = new Thread(() -> {});
System.out.println(thread.getState()); // 输出 NEW 

现在有两个问题:

  1. 反复调用同一个线程的start()方法是否可行?
  2. 假如一个线程执行完毕(此时处于TERMINATED状态),再次调用这个线程的start()方法是否可行?

从源码分析:

 public synchronized void start() {
      
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

从start0()看不出任何对state的操作。 我们只能从threadStatus 入手

可以看到 threadStatus 的变量。如果它不等于0,调用start()会直接抛出异常的。

下面用代码测试一下:

@Test
public void testStartMethod() {
    Thread thread = new Thread(() -> {}, "myThread");
    thread.start(); // 第一次调用
    thread.start(); // 第二次调用
}

第一次:
image.png

第二次:
image.png

而前面说到:如果它不等于0,调用start()会直接抛出异常的。故答案是否,不能start两次

image.png


那第二个问题: 假如一个线程执行完毕(此时处于 TERMINATED 状态),再次调用这个线程的start()方法是否可行?

从获取线程状态源码分析:

    // 返回线程的状态
    public State getState() {
        // get current thread state
        return sun.misc.VM.toThreadState(threadStatus);
    }
    
     public static Thread.State toThreadState(int var0) {
        if ((var0 & 4) != 0) {
            return State.RUNNABLE;
        } else if ((var0 & 1024) != 0) {
            return State.BLOCKED;
        } else if ((var0 & 16) != 0) {
            return State.WAITING;
        } else if ((var0 & 32) != 0) {
            return State.TIMED_WAITING;
        } else if ((var0 & 2) != 0) {
            return State.TERMINATED;
        } else {
            return (var0 & 1) == 0 ? State.NEW : State.RUNNABLE;
        }
    }

获取线程的状态是通过 toThreadState(int var0)函数获取的, 参数就是 threadStatus

通过 var0和 数字按位运算得到结果。

如果 (var0 & 4) != 0 成立,表示 var0 的第 3 位(从右往左数,从 0 开始计数)是 1,即表示线程状态为 RUNNABLE;如果 (var0 & 1024) != 0 成立,表示 var0 的第 11 位是 1,即表示线程状态为 BLOCKED。

回到刚才的问题,当线程处于 TERMINATED 状态, 说明 var0 的第3位为0, 即 threadStatus ! = 0

从之前的代码就可以看出会抛出 IllegalThreadStateException 异常。故答案也是否

4.1.2 RUNNABLE

Java线程的RUNNABLE状态其实是包括了传统操作系统线程的readyrunning两个状态。

4.1.3 BLOCKED

阻塞状态。处于BLOCKED状态的线程正等待锁的释放以进入同步区。

可以类比为一个人正在使用一部电话,而另一个人也想要使用电话。由于电话只有一个,因此另一个人必须等待当前使用者结束后才能使用。在此期间,他就处于blocked状态。因为他知道那部电话已经被占用了,不能立即使用。

4.1.4 WAITING

等待状态。处于等待状态的线程变成RUNNABLE状态需要其他线程唤醒。

调用如下3个方法会使线程进入等待状态:

  • Object.wait():使当前线程处于等待状态直到另一个线程唤醒它;
  • Thread.join():等待线程执行完毕,底层调用的是Object实例的wait方法;
  • LockSupport.park():除非获得调用许可,否则禁用当前线程进行线程调度。

4.1.5 TIMED_WAITING

超时等待状态。线程等待一个具体的时间,时间到后会被自动唤醒。自动唤醒后,拥有了去争夺锁的资格。

调用如下方法会使线程进入超时等待状态:

  • Thread.sleep(long millis):使当前线程睡眠指定时间;
  • Object.wait(long timeout):线程休眠指定时间,等待期间可以通过notify()/notifyAll()唤醒;
  • Thread.join(long millis):等待当前线程最多执行millis毫秒,如果millis为0,则会一直执行;
  • LockSupport.parkNanos(long nanos): 除非获得调用许可,否则禁用当前线程进行线程调度指定时间;
  • LockSupport.parkUntil(long deadline):同上,也是禁止线程进行调度指定时间;

4.1.6 TERMINATED

终止状态。此时线程已执行完毕。

image.png


weiweiyi
1k 声望123 粉丝