线程
线程(thread)是操作系统中最小的执行单位,它是进程中的一个实体,负责执行进程中的指令。一个进程可以包含多个线程,这些线程可以并发执行,从而提高系统的并发性能和资源利用率。
线程和进程不同,线程共享同一个进程的虚拟地址空间和系统资源,包括堆、栈、文件和网络连接等。由于线程之间可以共享数据和资源,因此在编程中使用线程可以更高效地利用系统资源和提高程序的并发性能。
线程的调度由操作系统的线程调度器负责,它根据线程的优先级和状态等信息来分配 CPU 时间。在多核处理器中,多个线程可以同时执行,从而实现并行计算。
线程还有一些概念,如线程安全、线程同步和线程阻塞等,这些概念都与多线程编程密切相关,是编写高效、稳定的多线程程序的关键。
进程
在计算机科学中,进程(process)是指在操作系统中正在运行的程序实例。一个进程可以包含一个或多个线程(thread),每个线程都是一个独立的执行流,可以独立地运行并与其他线程共享进程的资源。
每个进程都有自己的虚拟地址空间,包括代码、数据、堆栈和共享库等。操作系统通过分配资源和调度线程来管理进程,使进程之间彼此独立,从而保证系统的稳定性和安全性。
进程可以与其他进程进行通信,例如通过共享内存、管道、消息队列和套接字等机制来交换数据和同步操作。进程还可以通过创建子进程来执行新的程序实例。
进程是多任务操作系统的基本单位,每个进程都有自己的优先级和资源限制。操作系统通过调度算法来决定哪个进程获得 CPU 时间,并在必要时暂停或终止进程,以避免系统崩溃或资源耗尽。
并发并行
并发和并行是计算机科学中两个重要的概念,它们都与同时执行多个任务相关。
并发(concurrency)是指多个任务在同一时间间隔内启动和运行,但不一定同时执行。在并发模型中,任务可以以交替的方式执行,通过轮流使用 CPU 时间片来完成。这样可以使多个任务同时运行,从而提高系统的吞吐量。
与之不同的是,并行(parallelism)是指多个任务在同一时刻同时执行。在并行模型中,每个任务都有自己的处理器核心,可以并行执行。并行可以更快地完成任务,但需要更多的硬件资源和更复杂的程序设计。
简而言之,如果是同时做多件事情,可以使用并发;如果是同一时间做多件事情,可以使用并行。
同步异步
同步和异步是两种不同的编程模式,用来描述消息传递或函数调用的机制。
同步通常指调用一个函数时,调用者需要等待函数的返回值或结果,然后再继续执行下一步操作。在同步调用的过程中,调用者会被阻塞,直到函数执行完成并返回结果。同步调用通常是顺序执行的,也就是说,必须等到前面的任务完成后才能执行后面的任务。
异步通常指调用一个函数时,调用者不需要等待函数的返回值或结果,而是继续执行下一步操作。在异步调用的过程中,调用者不会被阻塞,函数会在后台执行,执行完成后会通知调用者。异步调用通常是并发执行的,也就是说,前面的任务和后面的任务可以同时执行。
在实际编程中,同步和异步通常使用不同的机制来实现。同步通常使用函数调用、返回值、阻塞等机制,而异步通常使用回调、事件、消息队列等机制。
线程池
线程池是一种多线程处理方式,它可以有效地控制线程的创建和销毁,提高系统的稳定性和效率。线程池中包含了若干个线程,这些线程可以执行一些任务,任务可以提交到线程池中,线程池会从中选择一个线程来执行任务,执行完成后该线程会返回到线程池中等待下一个任务。
线程池的好处有很多,包括:
- 降低资源消耗:线程池中的线程可以被重复利用,避免了频繁创建和销毁线程的消耗。
- 提高系统响应速度:线程池可以避免任务等待的时间,减少任务执行的延迟,提高系统的响应速度。
- 提高系统稳定性:线程池可以限制线程的数量和资源消耗,避免系统崩溃或死机的情况。
Java中的线程池实现了java.util.concurrent.Executor接口,它包含了若干个线程和一个任务队列,可以处理由java.util.concurrent.ExecutorService.submit()方法提交的Runnable或Callable任务。Java中的线程池有几种类型,包括FixedThreadPool、CachedThreadPool、SingleThreadPool和ScheduledThreadPool等,可以根据不同的场景和需求选择不同的线程池类型。
使用线程池的步骤通常包括:创建线程池、创建任务、将任务提交到线程池、等待任务完成、关闭线程池等。需要注意的是,在使用线程池时需要合理配置线程池的大小、任务队列的长度等参数,避免线程池中的线程数过多或任务队列过长,导致系统资源的浪费和效率的降低。
线程三大特性(原子性、可见性、有序性)
线程的三大特性是指原子性、可见性和有序性,这三个特性是多线程编程中非常重要的概念。
- 原子性:原子性是指一个操作是不可被中断的,要么全部执行成功,要么全部执行失败。例如,对于一个计数器的自增操作,如果两个线程同时执行,那么它们可能会同时读取计数器的值,然后同时将其加一,导致计数器只增加了一次,这就是一个原子性问题。
- 可见性:可见性是指一个线程对共享变量的修改能够被其他线程及时的看到。例如,对于一个共享变量,如果一个线程对其进行了修改,那么其他线程也应该能够及时看到这个修改。
- 有序性:有序性是指在单个线程内,所有的操作都是有序的。但是在多线程环境中,由于线程的交替执行,操作的执行顺序是不可预测的,这就可能导致一些操作的执行顺序出现问题。例如,一个线程对共享变量进行了修改,但是由于指令重排等原因,其他线程可能会先看到这个变量的新值,而后看到这个变量的旧值,这就是一个有序性问题。
在多线程编程中,要保证原子性、可见性和有序性是非常重要的,可以使用synchronized、volatile、Lock等机制来保证这三个特性的正确性。同时,也需要注意避免一些常见的多线程编程问题,例如死锁、竞态条件等。
锁(乐观锁、悲观锁、死锁、可重入锁、读写锁、公平锁、非公平锁)
- 乐观锁:乐观锁认为并发访问是比较少的,因此采用乐观的策略,在访问共享资源时不加锁,而是采用版本控制的方式来保证数据一致性。
- 悲观锁:悲观锁认为并发访问是比较频繁的,因此采用悲观的策略,在访问共享资源时加锁,以保证数据一致性。
- 可重入锁:可重入锁是一种支持重入的锁,即同一个线程可以多次获得同一把锁,而不会被自己所持有的锁所阻塞。
- 读写锁:读写锁是一种特殊的锁机制,它允许多个线程同时读取共享资源,但只允许一个线程写入共享资源,从而提高了并发访问的效率。
- 公平锁:公平锁是一种能够保证多个线程按照其请求锁的顺序获得锁的锁机制,可以避免某些线程一直获取不到锁的问题。
- 非公平锁:非公平锁是一种不保证多个线程按照其请求锁的顺序获得锁的锁机制,可以提高并发访问的效率。
- 死锁:死锁是指两个或多个线程在等待对方释放资源的情况下,形成了一种僵局,导致它们都无法继续执行。
在使用锁的过程中,需要注意锁的粒度,尽量将锁的范围缩小到最小,以避免出现竞争和性能瓶颈。同时,也需要注意避免死锁等问题的发生。
JMM内存模型
JMM是Java内存模型(Java Memory Model)的缩写,它是Java多线程编程的核心所在,规定了Java虚拟机在执行多线程程序时各个线程之间共享数据的方式,以及线程之间通信的规则。
Java内存模型的主要目标是为了保证程序在各种平台下都能正确地执行,并且具有可移植性。Java内存模型定义了一些规则和约束,以确保在多线程环境下程序的正确性。
Java内存模型有以下几个特点:
- 原子性:Java内存模型确保对基本类型变量和引用变量的读取和赋值操作是原子性的,即一次读写操作是不可被中断的。
- 可见性:Java内存模型确保对于一个变量的写操作对于其他线程是可见的,即在一个线程修改了变量之后,其他线程应该能够立即看到这个变量的新值。
- 有序性:Java内存模型确保程序中各个操作的执行顺序是按照代码的顺序执行的,但是在多线程环境下,由于指令重排等原因,操作的执行顺序是不可预测的。
为了保证程序的正确性,Java内存模型定义了一些规则和约束,例如volatile关键字、synchronized关键字、final关键字、锁等机制,来确保程序在多线程环境下能够正确地执行。
总之,Java内存模型是Java多线程编程中非常重要的概念,了解它的规则和约束能够帮助我们编写正确、高效、可靠的多线程程序。
CPU时间片
CPU时间片(Time Slice)是指操作系统为了实现多任务处理所采用的一种技术。在这种技术中,操作系统将CPU时间分成若干个时间片,每个时间片分配给一个任务来执行。当一个时间片用完后,操作系统会将CPU的控制权交给另一个任务,以此来实现多任务处理。
在多任务处理中,每个任务都有一个运行状态,可以是就绪状态、运行状态或者阻塞状态。当一个任务的时间片用完后,它就会从运行状态转换到就绪状态,等待下一个时间片的到来。当所有任务都运行完一次后,操作系统会重新分配时间片,重新开始循环。
CPU时间片的大小是可以设置的,一般情况下,操作系统会根据系统负载来动态调整时间片的大小。如果系统负载较轻,那么时间片可以设置得较大,这样可以减少任务切换的次数,提高系统的效率。如果系统负载较重,那么时间片可以设置得较小,这样可以使任务切换更加频繁,保证每个任务都能够得到充分的执行时间。
总之,CPU时间片是多任务处理中的一种重要技术,它可以帮助操作系统实现多任务处理,提高系统的效率和响应能力。
上下文切换
上下文切换(Context Switch)是指在多任务处理系统中,从一个任务(进程或线程)切换到另一个任务时,需要保存当前任务的状态(也称为上下文),并将另一个任务的状态恢复。上下文切换通常由操作系统内核负责完成,它是多任务处理中的一项基本操作。
在多任务处理系统中,由于CPU只能执行一个任务,因此需要不断地切换任务,以便多个任务都能够得到充分的执行时间。当操作系统需要切换任务时,它会将当前任务的状态保存到内存中,包括CPU寄存器、程序计数器、内存映射表、栈指针等。然后,操作系统会将另一个任务的状态从内存中恢复到CPU寄存器中,并开始执行该任务。这个过程就是上下文切换。
上下文切换是一项非常耗费系统资源的操作,因为它需要保存和恢复大量的状态信息,并且涉及到内核态和用户态之间的切换。因此,在设计多任务处理系统时,需要尽量减少上下文切换的次数,以提高系统的效率和响应能力。
一些常见的减少上下文切换的方法包括:采用协程、减少进程和线程的创建和销毁、避免过度使用同步和互斥机制、采用非阻塞I/O等。