1、多线程的出现及挑战
编程的核心在于数据的处理与操作。随着技术的发展,为了充分利用多核CPU和各类系统资源,多线程编程成为了现代软件开发的重要组成部分。多线程允许程序同时执行多个任务,从而显著提高了数据处理效率和系统资源利用率。然而,多线程的引入也带来了并发问题,其中最核心的是并发三要素:可见性、原子性、有序性。
1.1 可见性
在多线程环境中,由于CPU缓存机制的存在,当一个线程修改了共享变量的值后,这个修改可能不会立即同步到主内存中。同时,其他线程可能还在使用它们各自CPU核心上的缓存中存储的旧的变量值,而不是主内存中的最新值。这种现象被称为“可见性问题”,因为它导致了一个线程对共享变量的修改对其他线程来说是不可见的,从而可能引发数据不一致或其他并发相关的问题。
为了解决可见性问题,编程语言或运行时环境通常会提供一系列同步机制,如内存屏障、volatile关键字、锁和原子操作等。这些机制可以确保当一个线程修改了共享变量的值后,这个修改会立即被同步到主内存中,并使得其他线程能够看到这个最新的值。通过使用这些同步机制,可以确保多线程程序中的可见性问题得到妥善解决。
1.2 原子性
原子性指一个操作或多个操作在执行过程中是不可中断的,即这些操作要么全部完成,要么完全不执行,中间不会被其他线程或操作打断。在多线程环境中,由于CPU资源有限,操作系统会采用时间片轮转的方式为每个线程分配CPU时间片,这意味着线程的执行可能会被频繁地打断和切换。
当多个线程共享某个资源(如内存中的变量)并尝试同时对其进行修改时,如果没有正确的同步机制来保证操作的原子性,就可能出现数据不一致的问题。例如,一个线程可能正在执行一个包含多个步骤的复合操作(如先读取一个值,对其进行计算,然后再写入主内存),但在执行过程中被另一个线程打断,导致该复合操作只完成了部分步骤就被其他线程观测到,从而造成数据竞争和不一致。
为了确保操作的原子性,可以使用多种同步机制,如互斥锁、信号量、原子变量等。这些机制确保在任何时候只有一个线程能够访问和修改共享资源,从而避免数据竞争和不一致的问题。
1.3 有序性
在单线程环境中,程序通常按照源代码中编写的顺序顺序执行。然而,当涉及多线程编程时,情况就变得复杂了。由于编译器优化和处理器对指令的并行处理能力,指令的执行顺序可能与源代码中编写的顺序有所不同,这种现象被称为指令重排序。虽然指令重排序是为了提高程序的执行效率,但它同时也可能引发多线程程序中的数据竞争和内存可见性问题。
2、线程生命周期
线程的状态在Java中通常可以通过Thread.State枚举来表示,该枚举定义了线程可能处于的几种状态。这些状态包括:
(1)NEW(新建)
线程已被创建但尚未启动。即,线程对象已经通过Thread类或其子类的构造函数创建,但start()方法尚未被调用。
(2)RUNNABLE(可运行/运行中)
线程正在Java虚拟机中运行,或者等待获取CPU时间片来执行。这包括了正在CPU上执行的线程和处于就绪队列中等待操作系统调度的线程。由于线程调度由操作系统控制,Java虚拟机通常将这两种情况都视为RUNNABLE状态。
(3)BLOCKED(阻塞)
线程正在等待监视器锁(通常是内置锁)以便进入同步块或方法。当一个线程试图获取一个已经被其他线程持有的锁时,它会被阻塞,直到锁被释放。
(4)WAITING(无限期等待)
线程正在等待另一个线程执行某个特定动作。线程可以通过调用没有设置超时参数的Object.wait()方法、Thread.join()方法或者LockSupport.park()方法进入WAITING状态。线程将一直等待,直到其他线程通过notify()、notifyAll()或unpark(Thread)等方法将其唤醒。
(5)TIMED_WAITING(限时等待)
线程正在等待另一个线程执行某个动作,但设置了等待的超时时间。线程可以通过调用设置了超时参数的Thread.sleep(long millis)、Object.wait(long timeout)、Thread.join(long millis)、LockSupport.parkNanos(long nanos)或LockSupport.parkUntil(long deadline)等方法进入TIMED_WAITING状态。如果等待时间超过了设定的超时时间,线程将自动唤醒。
(6)TERMINATED(终止)
线程已经执行完毕,或者因为异常而退出。线程执行完run()方法中的所有代码后自然终止,或者因为未捕获的异常而异常终止。
3、线程的创建
Java提供了多种创建线程的方式。
3.1实现 Runnable 接口
实现 Runnable 接口创建线程,避免了单继承限制,相对灵活,无法获取返回值。
3.2实现 Callable 接口
实现 Callable 接口创建线程,支持任务执行完毕后返回结果或处理异常,可以结合Future机制获取线程执行结果的情况。
3.3继承 Thread 类
继承 Thread 类创建线程,相对简单,由于Java单继承的特性,如果已经继承了其他类,则无法使用这种方式创建线程。
3.4线程池创建线程
线程池创建线程是比较推荐的线程获取方式,可以通过线程池灵活管理线程的生命周期,复用线程,降低线程创建和销毁的开销。
4、线程中断机制
线程的中断方式有两种:
(1)调用stop方法
由于直接调用Thread提供的stop方法会有很多安全问题,比如,数据不一致,死锁,文件连接相关资源未正确释放等等。因此,一般的场景不会使用该方法终止线程的执行。
(2)利用interrupt方法和机制
interrupt方法可以将对应线程的中断状态设置为true。Thread.interrupted()获取当前线程的中断状态。如果要中断某线程。可以通过设置线程的中断状态为true+线程本身执行添加判断条件执行if (Thread.interrupted())
本章节到此为止!