百度是不是只有在自己的网站发布才会被收录,wordpress rest 认证,石家庄菜谱设计公司,中国大数据平台官网一、用户态与内核态 【概念】
用户态是指用户程序运行时的状态#xff0c;在这种状态下#xff0c;CPU只能执行用户态下的指令#xff0c;并且只能访问受限的内存空间
内核态是操作系统内核运行时的状态#xff0c;内核是计算机系统的核心部分#xff0c;CPU可以执行所有…
一、用户态与内核态 【概念】
用户态是指用户程序运行时的状态在这种状态下CPU只能执行用户态下的指令并且只能访问受限的内存空间
内核态是操作系统内核运行时的状态内核是计算机系统的核心部分CPU可以执行所有指令可以访问所有内存空间
【 两者切换原因】
当用户程序需要执行一些需要操作系统支持的操作时需要将用户态切换到内核态
【举例】
线程的阻塞与唤醒就需要用户态与内核态切换
当线程被阻塞时线程会从用户态切换到内核态操作系统内核会处理阻塞请求将线程的状态设置为阻塞并将其添加到等待队列中
等线程被唤醒时操作系统会在内核态中将线程的状态改为就绪并将其从等待队列中移除切换到用户态后继续执行用户态下的指令
【注意】
用户态与内核态之间切换的开销非常大因此减少不必要的用户态与内核态之间的切换对于系统性能和效率提高很重要
二、锁策略
2.1 什么是锁策略
锁策略是指在多线程编程中这把锁在加锁、解锁、锁冲突时都会怎么做 2.2 乐观锁 vs 悲观锁
悲观锁认为多个线程访问同⼀个共享变量冲突的概率较大会在每次访问共享变量之前都去真正加锁 乐观锁认为多个线程访问同⼀个共享变量冲突的概率不大并不会真的加锁而是直接尝试访问数据 在访问的同时识别当前的数据是否出现访问冲突.
【Java中synchronized是哪种锁】
synchronized既是乐观锁也是悲观锁因为它支持自适应
synchronized在开始的时候会使用乐观锁当发现锁竞争的次数增加时会切换为悲观锁 2.3 重量级锁 vs 轻量级锁
一般认为悲观锁就是重量级锁乐观锁就是轻量级锁
重量级锁加锁的过程做的事情多——重量轻量级锁加锁的过程做的事情少——轻量
synchronized是一个轻量级锁如果锁冲突比较严重就会变成重量级锁 2.4 自旋锁 vs 挂起等待锁
自旋锁是轻量级锁的一种典型实现方式下面是自旋锁一段伪码
while (true) {if (锁是否被占用) {continue;}获取到锁break;
}
CPU在忙等、空转如果获取锁失败就立即再尝试获取锁无限循环直到获取到锁为止消耗了更多的CPU资源但是锁一旦被释放就会第一时间拿到锁
自旋锁轻量的原因一方面自旋锁避免了线程的阻塞与唤醒的开销减少了性能的消耗另一方面自旋锁一般适用于线程占用锁时间较少的场景不会造成过多CPU资源
拿到锁的速度更快但消耗CPU
挂起等待锁是重量级锁的一种典型实现方式借助系统中的线程调度如果当前锁被占用该线程尝试获取锁就会挂起阻塞状态直到这个锁被释放系统调度到这个线程该线程才会尝试获取这个锁
挂起等待锁重量的原因需要进行线程的阻塞与唤醒有较多的用户态与内核态之间的切换重量
拿到锁的速度更慢节省CPU
synchronized 轻量级锁部分是基于自旋锁实现的重量级锁部分是基于挂起等待锁实现的 2.5 可重入锁 vs 不可重入锁
可重入锁同一个线程针对同一把锁连续加锁两次不会死锁
不可重入锁同一个线程针对同一把锁连续加锁两次会死锁
synchronized是可重入锁 2.6 公平锁 vs 非公平锁
公平锁严格按照先来后到的顺序来获取锁哪个线程等待的时间长哪个线程就先拿到锁
非公平锁多个线程随机获取到锁和线程等待时间无关
synchronized属于非公平锁 2.7 互斥锁 vs 读写锁
synchronized是互斥锁
读写锁是一个比较特殊的锁先来看下面几个有关线程安全场景
两个线程只读一个共享数据不会发生线程安全问题两个线程写一个共享数据会发生线程安全问题两个线程一个都一个写会发生线程安全问题
读写锁拥有一下功能
读锁和读锁之间不会发生互斥——有利于降低锁冲突的概率写锁和写锁之间会发生互斥读锁和写锁之间会发生互斥
synchronized不是读写锁因为加上synchronized后即使是两个都只读共享变量也会产生互斥
三、synchronized 实现原理
3.1 特点
既是悲观锁也是乐观锁既是轻量级锁也是重量级锁轻量级锁基于自旋锁实现重量级锁基于挂起等待所实现可重入锁非公平锁是互斥锁不是读写锁
3.2 synchronized 自适应 什么是偏向锁
代码首次执行synchronized对对象加锁时并不是真正加锁而是作一个标记如果后续没有其他线程针对这个对象加锁的话就一直保持这种状态直到解锁这样就减少了系统开销
当后续有其他线程占用同一个锁对象加锁时才会真正加锁此时就已升级成了轻量级锁 3.3 锁消除
锁消除是一种锁优化策略
当在代码中写了加锁的操作编译器JVM会对你当前的代码进行检查看这个锁加的是否合适如果完全没必要加锁就会把加锁操作优化掉
比如在单线程的环境下进行加锁操作该操作就会被编译器优化掉 3.4 锁粗化
锁的粒度当加锁的范围内进行的操作越多锁的粒度越粗反之锁的粒度越细
在保证逻辑等价的情况下为了避免频繁加锁解锁编译器会将多次细粒度的锁合并成一次粗粒度的锁 四、CAS
4.1 什么是CAS
CAScompare and swap意为比较和交换一个CAS设计以下操作 假设内存中的值为V旧的预期值为A要修改的值为B 比较V与A是否相等如果相等则将B写入V交换返回操作是否成功
下面是一段CAS的伪码
boolean CAS (address, exceptValue, swapValue) {if (address exceptValue) {address swapValue;return true;}return false;
}
注意上述代码并不是原子的真实的CAS是一个原子硬件指令改代码只是辅助理解
当多个线程针对某一资源进行CAS操作只有一个线程操作成功但是其他线程并不会阻塞而是收到操作失败的信号 4.2 CAS是怎么实现的
简而言之,是因为硬件方面提供了支持软件层面才可以做到由于CPU提供了CAS对应的硬件指令因此操作系统内核也能够完成这样的操作之后OS会提供出CAS的apiJVM对OS提供的api进一步的封装,我们便可以在Java中使用CAS操作了 4.3 CAS 的应用
1原子类
标准库中提供了 java.util.concurrent.atomic 包里面的类都是基于CAS实现的原子类 我们以 AtomicInteger 类为例
public class Demo {public static void main1(String[] args) {AtomicInteger count new AtomicInteger(1);count.getAndIncrement(); // countcount.incrementAndGet(); // countcount.getAndDecrement(); // count--count.decrementAndGet(); // --countcount.getAndAdd(100); //count 100}
}
上述代码的加加减减操作都是原子的没有用到任何加锁操作 接下来以其中一个方法为例进行详细剖析看getAndIncrement()的伪代码
class AtomicInteger {private int value;public int getAndIncrement() {int oldValue value;while ( CAS(value, oldValue, oldValue1) ! true) {oldValue value;}return oldValue;}
}
假设两个线程同时调用getAndIncrement
1. 两个线程都读取value的值到oldValue中oldValue是一个局部变量每个线程都有自己的栈 2. 线程1先执行CAS发现oldValue 和 value 相同则直接对value 赋值 oldValue 1注意这里是getAndIncrement所以先获取再加加所以返回的是oldValue但其实value已经加1了 3. 线程2再执行CAS的时候发现value 和 oldValue不相等则进入循环在循环里重新获取value的值并赋值给oldValue 4. 线程2第二次执行CAS发现oldValue 和 value相同于是执行赋值操作 5. 线程1和线程2针对同一个变量进行加加操作整个过程线程是安全的并且没有用到锁 2实现自旋锁
上述线程2在循环中重新将value赋值给oldValue的操作很像自旋锁的实现逻辑实际上自旋锁就是基于CAS实现的来看伪代码
public class SpinLock {private Thread owner null; //此时owner处于未加锁状态public void lock(){while(!CAS(this.owner, null, Thread.currentThread())){}}public void unlock (){this.owner null;}
}
代码中owner用来追踪加锁的线程如果为null代表代码中没有任何一个线程加锁接下来有一个线程1调用lock()方法进行加锁执行CAS发现owner为空则直接进行加锁并owner指向这个加锁的线程CAS执行成功返回true取反后跳出while循环
此时又来一个线程2也要进行加锁假设这里锁对象和线程1相同调用lock()方法发现owner不为空说明有其他线程进行了加锁那就进入循环并不断尝试CAS操作
当线程1解锁后调用unlock()方法此时owner为空线程2执行CAS操作成功成功加锁并跳出循环 4.4 CAS 的 ABA 问题
CAS的核心是比较发现相等→交换CAS希望的是数据从来没改变过相等但是某些情况可能会有其他线程将数据从A→B→ACAS并不能判断数据中途是否有发生改变,这就是ABA问题
ABA在一些极端情况下可能产生bug开下面一段取款的伪代码
void 取款 () {int oldBalance balance; // balance 为当前账户余额// CAS执行成功取款500while (!CAS(balance, oldBalance, balance - 500)) {}}
假如我的初衷就是取500块钱取款机创建了两个线程来并发执行-500操作我们希望一个-500成功一个-500失败 此时如果加一个转账的操作就会引发bug 如何避免ABA问题
上述场景中用余额来判定本身就不太科学因为余额会发生改变容易引发ABA问题
引入版本号约定版本号只能加 不能减每次操作余额版本号都要1如果版本号没有改变余额就一定没有改变过 本篇文章到此结束