Java并发学习笔记-线程
锁机制
1.特性
- 互斥性:同一时间只允许一个线程拥有某个对象的锁。
- 可见性:必须确保在锁释放前,对共享变量所做的修改,对其他线程是可见的。
挂起、休眠、阻塞、非阻塞
- 挂起(Suspend):当线程被挂起的时候,会失去CPU的使用时间,直到被其他线程(用户线程或调度线程)唤醒。(在当前JDK版本中,Thread的suspend方法被废弃,挂起是JVM的系统操作,其实是在申请资源失败时的阻塞)
- 休眠(Sleep):当线程进入休眠状态时,会失去CPU的使用时间,但在过了指定的休眠时间之后,它会自动激活,无需唤醒(整个唤醒表面看是自动的,但实际上也得有守护线程去唤醒,只是不需编程者手动干预)。
- 阻塞(Blocked):在线程执行时,所需要的资源不能得到,则线程被挂起,直到满足可操作的条件。(eg. CompletableFuture的get()方法)
- 非阻塞(non-blocking):在线程执行时,所需要的资源不能得到,则线程不是被挂起等待,而是继续执行其余事情,待条件满足了之后,收到了通知(同样是守护线程去做)再执行。(eg. CompletableFuture的supplyAsync()方法)
wait和sleep的区别
上文提到了sleep和wait都可以进入休眠状态,下文将详细说明二者的区别:
- sleep() 方法来自于 Thread 类,而 wait() 方法来自于 Object 类。因此,sleep() 方法可以在任何地方使用,而 wait() 方法必须在同步的上下文中使用,即wait 方法必须配合 synchronized 一起使用,否则在运行时就会抛出 IllegalMonitorStateException 的异常。
- sleep() 方法会使当前线程进入阻塞状态(blocked),暂时释放CPU资源,但保持对锁的持有状态。当指定的时间过去后,线程会重新进入可运行状态(runnable)。
- wait() 方法会使当前线程进入等待状态(waiting),释放对象的锁,让其他线程可以获取该锁。调用 wait() 方法后,线程会等待其他线程调用相同对象上的 notify() 或 notifyAll() 方法来唤醒它,或者等待指定的时间,如果超过时间仍未被唤醒,线程会自动被唤醒。
- wait() 方法通常与条件(condition)一起使用,可以通过 notify() 或 notifyAll() 方法来唤醒等待的线程。这样可以实现线程之间的协调和通信。
总结:sleep() 方法用于暂时暂停当前线程的执行,而 wait() 方法用于将当前线程置于等待状态,并释放对象锁。sleep() 方法没有条件和通知机制,而 wait() 方法需要与 notify() 或 notifyAll() 方法一起使用。
线程状态
挂起、休眠、阻塞、非阻塞是与线程执行和资源等待相关的概念,而图中提到的线程状态是指Java中线程的状态。
- 等待阻塞:线程因为等待某个条件的满足而进入阻塞状态。例如,线程调用了wait()方法,等待其他线程发出的通知或特定条件的满足。在等待期间,线程会释放持有的锁并进入等待队列,直到被唤醒或满足条件后才能继续执行。
- 同步阻塞:线程因为获取对象的锁失败而进入阻塞状态。当线程想要进入一个被其他线程持有的同步块或同步方法时,它会被阻塞,直到获取到锁资源后才能继续执行。
- 其他阻塞:通过调用线程的 sleep()或join()或发出I/O请求 时,线程会进入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕 时,线程重新转入就绪状态;
内核态与用户态
概念
- 内核态允许执行系统调用,常用的操作包含文件操作、网络通信、进程管理、设备访问等。
- 当一个任务(进程)执行系统调用而进入内核指令执行时,进程处于内核态。
- 当任务(进程)执行自己的代码时,进程就处于用户态。
频繁切换内核态与用户态
频繁从用户态切换到内核态可能会导致以下几个方面的影响:
- 性能影响:从用户态切换到内核态需要切换上下文,并执行一些额外的操作,如保存和恢复寄存器状态、切换内存映射等。这些操作会带来额外的开销,导致系统的性能下降。
- 资源占用:内核态拥有更高的权限和更广泛的系统资源访问权限,因此过多的内核态操作可能导致系统资源的过度占用,例如内存、CPU时间片等。这可能导致其他应用程序的响应性降低或资源竞争。
- 安全风险:内核态是操作系统内核运行的特权级别,具有更高的权限。频繁切换到内核态意味着执行了更多的系统调用,这增加了应用程序的潜在风险,因为每次进入内核态都有可能暴露应用程序的漏洞或受到恶意代码的攻击。
- 上下文切换开销:频繁的用户态到内核态切换会增加上下文切换的次数,这是指在不同线程或进程之间切换执行的过程。上下文切换本身就是一项开销较大的操作,因为需要保存和恢复线程或进程的执行状态。频繁的切换会增加系统开销和延迟。
线程与进程
- 进程是资源分配的基本单位,而线程是调度的基本单位。
- 进程拥有独立的资源,包括内存、文件和设备等,而线程共享进程的资源。
- 同一个进程中的多个线程可以并发执行,共享进程的上下文,可以访问相同的数据。
进程之间相互独立,拥有独立的地址空间和资源,彼此之间不能直接通信,而线程之间可以通过共享内存等方式进行通信。 - 线程共享堆空间,拥有自己独立的栈空间
TODO:持续学习补充中
参考文章 https://www.jianshu.com/p/68e230cc2504