1、用户态与内核态
1.1 什么是用户态与内核态
用户态和内核态是操作系统的两种运行状态,这两个运行状态的区别主要是为了对访问能力进行限制,用户态的权限较低,而内核态的权限较高。用户态是指应用程序运行的环境,而内核态是指操作系统内核运行的环境。
- 用户态:用户态运行的程序只能受限地访问内存,只能直接读取用户程序的数据,并且不允许访问外围设备,用户态下的 CPU 不允许独占,也就是说 CPU 能够被其他程序获取。
- 内核态:内核态运行的程序可以访问计算机的任何数据和资源,不受限制,包括外围设备,比如网卡、硬盘等。处于内核态的 CPU 可以从一个程序切换到另外一个程序,并且占用 CPU 不会发生抢占情况。
1.2 用户态和内核态是如何切换的?
为什么要进行切换呢?
-
在用户态下,应用程序可以执行一般的操作,如读写文件、网络通信等。但是,有些特权操作,如访问硬件设备、修改内存映射等,需要通过系统调用来请求内核执行。这时,操作系统会将应用程序的状态保存起来,并切换到内核态执行相应的操作。
-
在内核态下,操作系统具有更高的权限和更广泛的资源访问能力。它可以执行特权指令、访问硬件设备、管理内存等。当内核完成相应的操作后,会将结果返回给应用程序,并将控制权切换回用户态。
-
用户态和内核态的切换是为了保护系统的安全性和稳定性。通过限制应用程序对系统资源的直接访问,可以防止恶意程序对系统造成破坏。同时,通过将特权操作交给内核执行,可以确保这些操作的正确性和一致性。
用户态和内核态的切换主要发生在以下几种情况:
-
系统调用:当用户程序需要请求操作系统提供的服务时,如读写文件、创建进程等,需要通过系统调用来实现。这时,会发生从用户态到内核态的切换。
-
中断处理:当硬件设备发生中断请求时,操作系统需要处理这个中断,这时,会发生从用户态到内核态的切换。
-
异常处理:当用户程序运行出错,如除0错误、访问非法内存等,操作系统需要处理这个异常,这时,会发生从用户态到内核态的切换。
用户态和内核态的切换过程如下:
1.保存当前程序的状态:包括程序计数器、寄存器等。
2.设置内核栈:因为用户栈和内核栈是分开的,所以需要切换到内核栈。
3.设置权限:将CPU的运行级别从用户态切换到内核态。
4.跳转到内核程序:根据中断向量表,跳转到相应的内核程序进行处理。
5.恢复程序状态:处理完后,恢复原来程序的状态,包括程序计数器、寄存器等。
6.恢复权限:将CPU的运行级别从内核态切换回用户态。
2、轻量级锁与重量级锁
2.1 什么是轻量级锁与重量级锁
重量级锁:我们知道,当要进入一个同步、线程安全的方法时,是需要先获得这个方法的锁的,退出这个方法时,则会释放锁。如果获取不到这个锁的话,意味着有别的线程在执行这个方法,这时我们就会马上进入阻塞的状态,等待那个持有锁的线程释放锁,然后再把我们从阻塞的状态唤醒,我们再去获取这个方法的锁。这种获取不到锁就马上进入阻塞状态的锁,我们称之为重量级锁。重点在于获取不到锁的线程,会一直处在等待状态,什么东西都做不了
- 优点:线程竞争不使用自旋,不会消耗CPU。
- 缺点:线程阻塞,响应时间缓慢。
轻量级锁:一般都是用自旋锁+CAS来实现的,我们可以定义自选的次数,到达这个次数后,我们就退出,不抢这个锁了,然后走对应的逻辑;或者升级为重量级锁
-
优点:竞争的线程不会阻塞,提高了程序的响应速度。
-
缺点:如果始终得不到锁竞争的线程使用自旋会消耗CPU,因为执行CAS原语是要靠CPU的。
2.2 轻量级锁与重量级锁的区别
轻量级锁和重量级锁是Java中两种不同的锁机制,它们在多个方面有以下区别:
-
用户态和内核态:
- 轻量级锁主要在用户态下执行,当线程竞争不激烈时,使用轻量级锁可以避免在用户态和内核态之间切换的额外消耗,从而提高性能。
- 重量级锁则需要切换到内核态执行,当线程竞争激烈时,使用重量级锁可以避免大量的CPU资源浪费在无效的轮询上。
-
是否阻塞当前线程:
- 轻量级锁在获取锁失败时,会采用自旋的方式尝试获取锁,不会阻塞当前线程。但如果自旋次数过多,仍无法获取到锁,那么轻量级锁会膨胀为重量级锁,此时会阻塞线程。
- 重量级锁在获取锁失败时,会直接将线程阻塞,直到获取到锁为止。
-
锁的状态:
- 轻量级锁在锁对象的Mark Word中存储了锁定线程的栈帧信息,当锁被释放时,系统通过CAS操作来进行锁的释放。
- 重量级锁在锁对象的Mark Word中存储了指向重量级锁(Monitor)的指针,当锁被释放时,需要唤醒被阻塞的线程。
-
使用场景:
- 轻量级锁适用于线程冲突较少的情况,因为线程冲突少,线程有很大概率在第一次就获取到锁,避免了系统切换的开销。
- 重量级锁适用于线程冲突较多的情况,因为线程冲突多,自旋很可能无法在限定次数内获取到锁,此时阻塞线程反而能更好地节省CPU资源。
-
性能开销:
- 轻量级锁的性能开销主要来自于CAS操作和自旋等待,但在竞争不激烈的情况下,这些开销通常可以被接受。
- 重量级锁的性能开销主要来自于线程切换和线程阻塞唤醒,这些开销在竞争激烈的情况下是必要的,因为它们可以避免CPU资源的浪费。
总的来说,轻量级锁和重量级锁各有优势,适用于不同的场景,Java虚拟机会根据具体情况动态地选择使用轻量级锁还是重量级锁。