Java中的锁
这篇博客主要讲一下Java中的各种各样五花八门的锁。这些五花八门的锁肯定不是工程师没事做闲着无聊倒腾出来的,他们都是为了在某些环境下的性能而对传统的锁做了些改造。
公平锁和非公平锁
公平锁是指当多个线程在等待同一锁时,必须按照申请锁的先后顺序排成一个等待队列依次来一次获得锁。
非公平锁是指加锁时不用考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待,是抢占式的。
对比:非公平锁的性能比公平锁搞5~10倍,因为公平锁需要在多核的情况下维护一个队列。但是非公平锁有可能会导致饥饿问题。
自旋锁
由于Java的线程是映射到操作系统的原生线程上的,如果要阻塞一个线程或者唤醒一个线程,都需要操作系统来帮忙完成,需要操作系统从用户态转换到核心态。因此状态转换需要耗费很多处理器时间。同时虚拟机的开发团队注意到许多应用上共享数据的锁定状态只会持续很短的一段时间,如果一个线程只是很短暂的占用一个共享资源,当在它短暂占用这段资源的时候如果有别的线程试图占用这个资源的时候,后来的线程会因为该资源被占用而陷入阻塞状态,丢失对时间片的占用,这会导致资源的大量浪费。所以针对这种情况开发团队设计出了自旋锁,即如果物理机器有一个以上的处理器,可以让两个或两个以上的线程同时并行执行,我们就可以让后面的线程“稍等一下”,但是不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只需让线程执行一个忙循环(自旋),等待锁被释放。
但是自旋也不可以替代锁,自旋虽然避免了线程切换的开销,但是它是要占用处理器时间的,因此,如果锁被占用的时间很短,自旋等待的效果会非常好。如果锁被占用的时间很长。那么自旋只会白白占用处理器的资源。因此自旋等待时间必须要有一定限制,如果自旋超过一定次数(默认是10次,使用-XX:PreBlockSpin来更改)没有获得锁,就应该使用传统的方式去挂起线程了。需要注意的是自旋是在轻量级锁中使用的,在重量级锁中,线程不能用自旋锁。
偏向锁
偏向锁是JDK6中引入的一项锁优化,它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。
偏向锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要同步。大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。
悲观锁和乐观锁
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。
乐观锁:假定不会发生并发冲突,只在提交操作时检测是否违反数据完整性。(使用版本号或者时间戳来配合实现)
共享锁和排它锁
共享锁:如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排它锁。获准共享锁的事务只能读数据,不能修改数据。
排它锁:如果事务T对数据A加上排它锁后,则其他事务不能再对A加任何类型的锁。获得排它锁的事务即能读数据又能修改数据
读写锁
读写锁是一个资源能够被多个读线程访问,或者被一个写线程访问但不能同时存在读线程。Java当中的读写锁通过ReentrantReadWriteLock实现。
还有其他的一些锁这里不一一列举。