在面试过程中,多线程及并发编程已经是必问的项目,并发编程的实现依赖Java中的锁机制,那么在Java中存在几种锁机制呢?
- synchronized java 原生的锁机制,它是悲观锁的处理理念,由JVM直接管控。
- AQS 全称AbstractQueuedSynchronizer ,它是由一个大佬设计的锁框架,它是乐观锁的处理理念。
铺垫
什么是悲观锁?
悲观锁就是在处理并发线程时采取一种悲观的态度,它的设计理念是,不论并发是否发生我都假设它一定发生(在某些情况下,大部分线程执行的都是读操作),采取这种设计理念,当线程持有锁后,其他线程不论执行什么操作都会被挂起,直到上一个线程释放锁资源。这种设计理念虽然保证了线程安全,但对系统的性能是大幅度的削弱,因此在很多情况下悲观锁并不是最优解。
什么是乐观锁?
乐观锁就是在处理并发线程时采取一种乐观的态度,它的设计理念是,在读取数据时,乐观锁并不会对数据加锁,而是允许其他线程同时读取数据,只有到数据提交的时候才通过一种机制来验证数据是否存在冲突(采取版本号控制或者CAS控制)
什么是CAS?
CAS全称comparable and swap,设计CAS的目的就是为了实现乐观锁,当两个线程同时去获取锁时,他们会去抢着修改一个变量state,这个变量由volatile修饰并且对所有线程可见(不懂看这篇),谁先抢到这个变量并修改,就代表谁拥有了这个锁,而其他的线程则会不断去尝试获取锁,这个过程也成为自旋,自旋在代码中循环执行CAS,重复一定次数,默认十次,大量线程自旋会消耗cpu性能。
那CAS如何保证多线程修改变量的时候不会出现并发情况呢?
CAS在修改state值的时候调用了操作系统的底层指令保证了CAS的原子性。这也就是为什么说CAS必须是原子性的,在Java1.6之前并没有CAS,正是因为操作系统的支持,才能开发出了CAS。
线程自旋、自适应自旋锁和线程阻塞?
自旋锁是指线程在拿不到锁的时候会执行一个循环来尝试获取锁,这个操作是在cpu中完成的,因此在大量线程自旋时,cpu性能会严重受损。
自适应自旋锁:这种相当于是对上面自旋锁优化方式的进一步优化,它的自旋的次数不再固定,其自旋的次数由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定,这就解决了自旋锁带来的缺点。
线程阻塞是指在当线程拿不到锁之后会立即将线程阻塞,不会耗损cpu性能,但是大量线程阻塞会使操作系统频繁在内核态和用户态之间切换。
二者各有利弊,在后面介绍synchronized 会详细介绍Java如何实现自动切换两种模式。
铺垫已经结束,因为篇幅限制,后续synchronized 和AQS 将分两篇博客分别撰写。