1. 进程与线程

现代操作系统在运行一个程序时,会为其创建一个进程。在一个进程里可以创建多个线程,这些线程有自己独立的计数器,栈空间,并且能够访问进程的共享内存。

进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程是操作系统分配资源的的最小单元。
线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。线程是操作系统调度的最小单元。

为什么要使用多线程?

  1. 更多的处理器核心:更好的利用好多核处理器。
  2. 更短的响应时间。
  3. 更好的变成模型。

2. 线程的优先级

现代操作系统采用 分时调度,操作系统会分出一个个时间片,线程会被分配若干个时间片。当线程的时间片用完了就会发生线程调度,并等待下次分配。线程被分配到时间片的多少决定了线程使用处理器资源的多少,而线程优先级就是决定线程需要分配多少处理器资源的线程属性。

Thread thread = new Thread();
//	默认是 5
thread.setPriority(7);

3. 线程的状态

状态名称 说明
NEW 初始状态 线程被创建 但是还没有调用 start() 方法
RUNNABLE 运行状态 Java线程讲操作系统的就绪和运行状态笼统地称为 运行中
BLOCKED 阻塞状态 表示线程阻塞于锁
WAITNG 等待状态 表示线程进入等待状态 进入该状态表示当前线程需要等待其他线程的通知或中断
TIME_WATING 超时等待状态 不同于上面,可以在指定的时间自行返回
TERMINATED 终止状态 表示当前线程已经执行完毕

3.1 线程状态枚举类

Thread.State 
public enum State { 
    /** 
    ● Thread state for a thread which has not yet started. 
     */ 
    NEW,(新建) 
 

    /** 
● Thread state for a runnable thread.  A thread in the runnable 
● state is executing in the Java virtual machine but it may      
* be waiting for other resources from the operating system      
* such as processor. 
     */ 
    RUNNABLE,(准备就绪) 
 
    /** 
● Thread state for a thread blocked waiting for a monitor lock. 
● A thread in the blocked state is waiting for a monitor lock 
● to enter a synchronized block/method or 
● reenter a synchronized block/method after calling      
* {@link Object#wait() Object.wait}. 
     */ 
    BLOCKED,(阻塞) 
 
    /** 
● Thread state for a waiting thread. 
● A thread is in the waiting state due to calling one of the      
* following methods: 
● <ul> 
● <li>{@link Object#wait() Object.wait} with no timeout</li> 
● <li>{@link #join() Thread.join} with no timeout</li> 
● <li>{@link LockSupport#park() LockSupport.park}</li>      
* </ul> 
     * 
● <p>A thread in the waiting state is waiting for another thread to      
* perform a particular action. 
     * 
● For example, a thread that has called <tt>Object.wait()</tt> 
● on an object is waiting for another thread to call 
● <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on      
* that object. A thread that has called <tt>Thread.join()</tt>      
* is waiting for a specified thread to terminate. 
     */ 
    WAITING,(等待) 
 
    /** 
● Thread state for a waiting thread with a specified waiting time. 
● A thread is in the timed waiting state due to calling one of 
● the following methods with a specified positive waiting time: 
● <ul> 
● <li>{@link #sleep Thread.sleep}</li> 
● <li>{@link Object#wait(long) Object.wait} with timeout</li> 
● <li>{@link #join(long) Thread.join} with timeout</li> 
● <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li> 
● <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li> 
● </ul> 
     */ 
    TIMED_WAITING,(等待) 
 
    /** 
● Thread state for a terminated thread. 
● The thread has completed execution. 
     */ 
    TERMINATED;(终结) 
} 

3.2 线程状态变迁

在这里插入图片描述

lock 锁下的 condition 调用 await() 方法也会进入等待状态。

阻塞状态是线程阻塞在 synchronized 关键字修饰的方法或者方法块时的状态,但是阻塞在 java.concurrent 包中 Lock 接口的线程状态是等待状态,因为 java.concurrent 包中 Lock 接口对于阻塞的实现使用了 LockSupport 类中的相关方法。

3.3 wait/sleep 的区别

  1. sleep 是 Thread 的静态方法,wait 是 Object 的方法,任何对象实例都能调用。
  2. sleep 不会释放锁,它也不需要占用锁。wait 会释放锁,但调用它的前提是当前线程占有锁(即代码要在 synchronized 中)。
  3. 它们都可以被 interrupted 方法中断。
    //todo join方法呢?

4. Deamon 线程

Deamon 线程是一个支持型的线程(守护线程),主要作用是程序中的后台调度以及支持工作。
当一个 Java 虚拟机中不存在 非 Deamon 线程的时候,Java虚拟机将退出。

Thread thread = new Thread();
thread.setDaemon(true);

Deamon 线程中的 finally 块不一定能够执行到。

5. 启动和终止线程

构造线程

线程对象(Thread)的构造过程涉及一个关键的底层方法 init(),它负责线程的底层初始化工作。这个方法是线程对象构造的核心,但开发者通常不会直接调用它,因为它被封装在 Thread 的构造函数中。
init() 是 Thread 类中的一个 私有方法,所有 Thread 的构造函数最终都会调用它来完成以下任务:
● 初始化线程属性(名称、线程组、优先级、守护线程标志等)。
● 绑定线程任务(Runnable 对象)。
● 设置线程堆栈大小(通过 stackSize 参数)。
● 与 JVM 本地线程建立关联(通过本地方法 registerNatives())。

5.1 启动线程

在线程对象初始化完成后,调用 start() 方法就可以启动线程了。start() 方法的含义是:当前线程(即父线程)同步告知 Java 虚拟机,只要线程规划起空闲,就应立即启动调用 start() 方法 的线程。

5.2 中断线程

Thread 线程提供了 暂停/恢复/停止的 方法:suspend() / resume() / stop() 。但是这三个方法都被 标记为废弃(@Deprecated),因为其设计存在严重的安全性和可靠性问题。比如当调用上述方法暂停或者停止线程时,线程持有的资源并不会释放,容易造成死锁等问题。停止线程会强制停止线程,会使得很多资源来不及释放。

方法 废弃原因
suspend() 导致死锁,不释放锁,破坏线程安全。
resume() 依赖 suspend(),无法解决根本问题。
stop() 强制终止导致数据不一致和资源泄漏,违背“协作式线程管理”原则。

5.3 如何中断/停止线程

理解中断:中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作。其他线程通过调用该线程的 interrupt() 方法进行中断操作。
线程通过检查自身是否被中断来进行响应,可以通过 isInterrupted() 来判断是否被中断,也可以调用静态方法 Thread.interrupted() 对当前线程的中断标识位进行复位。

在这里插入图片描述

安全的终止线程演示:

public class GracefulThreadStopDemo {

    static class StoppableTask implements Runnable {
        // 自定义终止标志(volatile 保证可见性)
        private volatile boolean stopRequested = false;

        // 外部调用此方法请求终止
        public void requestStop() {
            stopRequested = true;
        }

        @Override
        public void run() {
            try {
                // 循环检查终止条件:自定义标志 + 中断标志
                while (!stopRequested && !Thread.currentThread().isInterrupted()) {
                    // 模拟工作任务(包含可能阻塞的操作)
                    doWork();
                }
            } finally {
                // 终止前的资源清理
                cleanup();
            }
        }

        private void doWork() {
            try {
                System.out.println(Thread.currentThread().getName() + ": Working...");
                // 模拟耗时操作(可被中断的阻塞操作)
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // 捕获中断异常,恢复中断标志,并退出循环
                System.out.println(Thread.currentThread().getName() + ": Interrupted!");
                Thread.currentThread().interrupt(); // 重新设置中断标志
            }
        }

        private void cleanup() {
            System.out.println(Thread.currentThread().getName() + ": Cleaning up resources...");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        StoppableTask task = new StoppableTask();
        Thread workerThread = new Thread(task, "WorkerThread");
        workerThread.start();

        // 主线程等待 3 秒后请求终止
        Thread.sleep(3000);
        task.requestStop();  // 通过自定义标志请求终止

        // 可选:同时发送中断信号(双重保险)
        // workerThread.interrupt();

        // 等待工作线程结束
        workerThread.join();
        System.out.println("Main: Worker thread terminated.");
    }
}

上面的代码有双重终止条件

while (!stopRequested && !Thread.currentThread().isInterrupted()) {
    // 执行任务...
}

stopRequested:通过 volatile 保证多线程可见性,外部调用 requestStop() 设置此标志。
Thread.currentThread().isInterrupted():检查线程的中断标志位,响应 interrupt() 请求。


6. 线程间通信

Java支持多个线程同时访问一个对象或者对象的成员变量,由于每个线程可以拥有这个变量的副本,所以在程序执行过程中,一个线程看到的变量并不一定是最新的。

6.1 volatile 和 synchronized机制

关键字 volatile 可以用来修饰一个成员变量,告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存。它能保证所有的线程对变量访问的可见效。

关键字 synchronized 可以修饰方法或者同步块,它主要起确保在同一个时刻只能用一个线程处于方法或者同步块中,从而保证了线程对变量访问的可见效和排他性。

6.2 等待通知机制

Java 提供了内置的等待/通知机制。
等待/通知机制是指一个 线程A 调用了对象O 的 wait() 方法进入等待状态,而另一个 线程B 调用了 对象O 的notify() 方法或者 notifyAll() 方法,线程A 收到通知后从 对象O 的等待状态返回,进而执行后序操作。
上述两个线程通过那 对象O 来完成交互,而对象上的 wait() / notify() / notifyAll() 就如同开关信号一样,用来完成等待方和通知方之间交互工作。

注意:

  1. 调用上述方法之前需要先获取来锁。
  2. 调用完 wait() 方法后,线程状态由运行状态转变为等待状态,并将当前线程放置到对象的等待队列。

6.3 thread.join()

当 线程A 执行了 线程B 的 join() 方法后,线程A 等待 线程B 终止之后才能从 该处返回。

public static void main(String[] args) {

    Thread threadB = new Thread(
            new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName());
                }
            }, "threadB"
    );

    Thread threadA = new Thread(
            new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    //	插队的效果
                    threadB.join();
                    System.out.println(Thread.currentThread().getName());
                }
            }, "threadA"
    );

    threadA.start();
    threadB.start();
}

执行结果如下:
threadB
threadA

6.4ThreadLocal


7. 并发与并行

7.1串行模式

串行表示所有任务都一一按先后顺序进行。串行意味着必须先装完一车柴才能运送这车柴,只有运送到了,才能卸下这车柴,并且只有完成了这整个三个步骤,才能进行下一个步骤。串行是一次只能取得一个任务,并执行这个任务。

7.2 并行模式

并行意味着可以同时取得多个任务,并同时去执行所取得的这些任务。并行模式相当于将长长的一条队列,划分成了多条短队列,所以并行缩短了任务队列的长度。并行的效率从代码层次上强依赖于多进程/多线程代码,从硬件角度上则依赖于多核 CPU。

7.3 并发

并发(concurrent)指的是多个程序可以同时运行的现象,更细化的是多进程可以同时运行或者多指令可以同时运行。但这不是重点,在描述并发的时候也不会去扣这种字眼是否精确,并发的重点在于它是一种现象, 并发描述的是多进程同时运行的现象。但实际上,对于单核心 CPU 来说,同一时刻只能运行一个线程。所以,这里的"同时运行"表示的不是真的同一时刻有多个线程运行的现象,这是并行的概念,而是提供一种功能让用户看来多个程序同时运行起来了,但实际上这些程序中的进程不是一直霸占 CPU 的,而是执行一会停一会。 (宏观并行)
要解决大并发问题,通常是将大任务分解成多个小任务, 由于操作系统对进程的调度是随机的,所以切分成多个小任务后,可能会从任一小任务处执行。这可能会出现一些现象:
● 可能出现一个小任务执行了多次,还没开始下个任务的情况。这时一般会采用队列或类似的数据结构来存放各个小任务的成果
● 可能出现还没准备好第一步就执行第二步的可能。这时,一般采用多路复用或异步的方式,比如只有准备好产生了事件通知才执行某个任务。
● 可以多进程/多线程的方式并行执行这些小任务。也可以单进程/单线程执行这些小任务,这时很可能要配合多路复用才能达到较高的效率
小结(重点)
并发:同一时刻多个线程在访问同一个资源,多个线程对一个点
并行:同一时刻 多项工作一起执行


8. 管程

管程(monitor)是保证了同一时刻只有一个进程在管程内活动, 即管程内定义的操作在同一时刻只被一个进程调用(由编译器实现). 但是这样并不能保证进程以设计的顺序执行
JVM 中同步是基于进入和退出管程(monitor)对象实现的,每个对象都会有一个管程 (monitor)对象,管程(monitor) 会随着 Java 对象一同创建和销毁
执行线程首先要持有管程对象,然后才能执行方法,当方法完成之后会释放管程,方法在执行时候会持有管程,其他线程无法再获取同一个管程.

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐