从轻松的乐观锁和悲观锁开讲
悲观锁 认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会加上锁,确保数据不会被别的线程修改。例如synchronized和Lock都是悲观锁。悲观锁适用写操作多的场景,显式的锁定之后再操作同步资源。
乐观锁 认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,在java中是通过使用无编程锁来实现,只是在更新数据的时候去判断,之前有没有别的线程更新了这个数据。
如果这个数据没有被更新,当前线程将自己的修改的数据成功写入,如果这个数据已经被其他线程更新,则根据不同的实现方式执行不同的操作,比如放弃修改、重试抢锁等。
判断规则 1 版本号机制version 2 最常采用的是CAS算法,java原子类中的递增操作就是通过CAS自选实现的。
乐观锁的适合读操作多的场景,不加锁的特点能够使读性能大大的增加,乐观锁直接操作同步资源是一种无锁算法。
阿里巴巴的java开发手册中强调
同步代码块实际上是用的monitorenter和monitorexcit指令
普通同步方法 调用指令将会检查方法的ACC_SYNCHONIZED 访问标识是否被设置。如果设置了执行线程 会将先持有锁monitor 再执行方法,如果方法执行完时释放锁
静态同步方法 ACC_STATIC和ACC_SYNCHONIZED访问标志区分是否为静态同步方法。
公平锁和非公平锁
公平锁是指多线程按照申请锁的顺序获取锁 先来后到;非公平锁 是指多线程获取锁的顺序并不是按照申请的顺序,有可能后申请的线程比先申请的线程优化获取锁,高并发情况下 有可能造成优先级翻转或者饥饿状态
问题 为什么会有公平和非公平锁的设计,为社么默认非公平锁
1 恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是对cpu的角度来说 这个时间差还是很明显的。所以非公平锁能充分利用cpu的时间碎片,尽量减少cpu空闲状态时间。
2 使用多线程很重要的考量点是线程切换的开销,当 线程采用非公平锁时 当一个线程请求锁获取同步状态,然后释放同步状态, 所以刚释放锁的线程在此刻再次获取同步状态的概率就变的非常大,所以就减少线程开销。
可重入锁又叫递归锁
可重入锁是指在同一个线程 在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象是同一个对象),不会因为之前已经获取过还没释放而阻塞。
Synchronized的重入的实现原理 1 任何一个对象都可以成为一个锁 2ObjectMonitor.hpp源码中如下
根据源码中每个锁对象拥有一个锁计数器和一个指向持有该锁的线程 的指针。
当执行monitorenter时如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,java虚拟机就会将该锁对象的持有锁线程设置为当前线程,并且计数器加1.
在目标锁对象的计数器不为零的情况 下,如果锁对象的持有线程是当前线程,那么java虚拟机将将计数器加1,否则需要等待,直到持有线程释放该锁。
当执行monitorexit时java 虚拟机则需要将锁对象的计数器减1 计数器为零代表释放了锁。
死锁及排查
死锁是指两个或两个以上的 线程在执行过程中,因争夺资源而造成一种相互等待的现象,若无力外力干涉那他们都将无法推行下去,如果系统资源充足,进程的资源请求能够获得满足,死锁出现的可能性很低,否则会因为争夺有限的资源而陷入死锁。
排查死锁的命令 jps jstack 进程号