多线程
锁策略
乐观锁和悲观锁
乐观锁在加锁之前预估当前锁冲突的概率不大,因此在进行加锁的时候就不会做太多的工作,加锁过程中做的事情比较少,加锁的速度可能就更快,但是更容易引入一些其他问题,效率快但是会消耗更多的cpu资源
悲观锁在加锁之前预估当前锁的冲突概率比较大,因此在加锁的时候就会做更多的工作,做的事情更加多,加锁的速度可能更慢,但是整个过程中不容易出现其他问题
轻量级锁和重量级锁
轻量级锁,加锁的开销比较小,加锁的速度更加快,也是乐观锁
重量级锁,加锁的开销比较大,加锁的速度更慢,也是悲观锁
自旋锁和挂起等待锁
自旋锁是轻量级锁的典型实现,进行加锁的时候搭配一个while循环如果加锁成功,循环结束,加锁不成功,不是进入阻塞放弃cpu,而是进行下一次循环,再次尝试获取锁,这个反复快速执行的过程就是自旋锁,使用自旋的前提就是锁冲突的概率不大,其他线程释放锁,就能第一时间拿到,加锁的线程特别多,自旋意义就不大
挂起等待锁是重量级锁的一种典型实现,进行加锁的时候搭配一个while循环如果加锁成功,循环结束,加锁不成功,进入阻塞等待主动放弃cpu,重新参与系统的调度,什么时候能够调度回cpu就不知道了
普通互斥锁:
类似于synchronized涉及到加锁和解锁
读写锁:
分为加读锁和加写锁
读锁和读锁之间不会起锁冲突(不会阻塞)
写锁和写锁之间会出现锁冲突(会阻塞)
读锁和写锁之间会出现锁冲突(会阻塞)
一个线程加读锁的时候,另一个线程只能读,不能写
一个线程加写锁的时候,另一个线程不能写也不能读
公平锁和非公平锁
遵循先来后到的规矩
可重入锁和不可重入锁
一个线程针对这把锁,连续加锁两次,不会死锁就是重入锁,synchronized是可重入锁
会死锁就是不可重入锁,系统自带的锁是不可重入锁
可重入锁中需要记录持有锁的线程是谁,加锁的次数的计数
synchronized的工作原理
synchronized是能够自适应的锁
1.偏向锁
核心思想就是懒汉模式,能不加锁就不加锁,能晚加锁就晚加锁,所谓的偏向锁,并非真的加锁而是做了一个非常轻量的标记
2.轻量级锁阶段
线程有竞争但是不多,通过自旋锁的方式来实现的,
优势:别的线程把锁释放了,就会第一时间拿到锁
劣势:对于cpu的消耗来说相对较高
synchronized的内部也会统计当前这个锁对象上有多少个线程参与锁竞争,锁的竞争多了就会升级成重量级锁
3.重量级锁阶段
线程多了,锁竞争多了,拿不到锁的线程就会进行阻塞等待,不会继续进行自旋,就会让出cpu,不会是cpu占用率太高
当线程释放锁的时候,系统就会随机唤醒一个线程来获取锁
锁消除
编译器发现代码不需要加锁时,不涉及线程安全的代码就会自动把锁干掉
锁粗化
会把多个细粒度的锁合并成粗粒度的锁
粒度就是synchronized后面的大阔号里的代码越少,粒度越细
对于多线程来说粒度越细有利于多个线程并发执行,并不是说粒度越细越好,有的时候也是希望粒度粗点,当线程需要三次加锁解锁加锁解锁加锁解锁,就可以合并成一个粗粒度的锁,这样可以提高效率,因为每次加锁都可能会涉及阻塞
CAS
compare and swap是一个特殊的cpu指令,工作内容是交换和比较,比较和交换的是内存和cpu寄存器,可以原子的完成很多复杂的操作,达成无锁化编程效果
1.基于CAS实现原子类
java标准库提供的原子类基于原子类实现的
这个代码是使用了原子类的方法,在后置++操作就是通过cas的方式实现的,且要修改的内容也没有加锁,但是也保证了线程安全,并且更加高效,不会涉及线程阻塞等待

2.实现自旋锁
通过cas看当前线程是否有锁,若有锁就自旋等待,没锁就尝试让线程加锁
3.ABA问题
当数值从A变成B,又在途中把数值变回A此时CAS不知到数值到底是变了还是没有变
解决方案:1.约定数据变化只能单向变化,2.对于本身必须双向变化的数据,可以引入一个版本号,且这个版本号只能增加不能减少,让CAS查看到底有没有变

浙公网安备 33010602011771号