首先,在计算机信息世界里,单机单线程时代没有锁的概念。自从出现了资源竞争,人们才意识到需要对部分场景的执行现场加锁, 昭告天下,表明自己“短暂”拥有(其实对于任何有形或无形的东西,拥有都不可能是永恒的)。计算机的锁也是从开始的悲观锁,发展到后来的悲观锁、偏向锁、分段锁等。锁主要提供了两种特性:互斥性和不可见性。因为锁的存在,某些操作对外界来说是黑箱进行的,只有锁的持有者才知道对变量进行了什么修改。
Java的锁分类有很多,有按上面提到的悲观、乐观思想的悲观锁、乐观锁;有按根据线程获取锁的抢占机制分为公平锁、非公平锁;有按锁是否线程持有或共享的独占锁、共享锁;有按线程获取其他线程持有锁是否被阻塞的可重入锁、不可重入锁等...。下面介绍这些锁的定义以及锁的实现。
悲观锁。是指假定共享资源在大多数情况下都处于被占用的状态,因此每次访问共享资源时都需要加锁,从而避免并发问题。
在Java中,synchronized关键字和ReentrantLock都是悲观锁的实现方式。
乐观锁。是指假定共享资源在大多数情况下都处于未被占用的状态,因此每次访问共享资源时都不加锁,而是采用版本号等机制来判断是否有并发修改。
在Java中,Atomic类和ConcurrentHashMap都是乐观锁的实现方式。
乐观锁加锁吗
1.乐观锁本身是不加锁的,只是在更新时判断一下数据是否被其他线程更新了,AtomicInteger便是一个例子。
2.有时乐观锁可能与加锁操作合作。例如MySQL在执行update时会加排它锁。但这只是乐观锁与加锁操作合作的例子。
公平锁。是指按照请求的顺序来获取锁,即先请求的线程先获取锁。
在Java中,ReentrantLock和ReentrantReadWriteLock都提供了公平锁的实现方式。
ReentrantLock公平锁的实现方式:使用构造函数ReentrantLock(true)创建公平锁。
公平锁的缺点是会导致大量线程的上下文切换,因此在高并发环境下可能会影响性能。
非公平锁。是指获取锁的顺序不是按照请求的顺序来进行的,而是直接尝试获取锁。
在Java中,ReentrantLock和ReentrantReadWriteLock都提供了非公平锁的实现方式。
ReentrantLock非公平锁的实现方式:使用默认的构造函数ReentrantLock()创建非公平锁。
非公平锁的优点是可以减少上下文切换,从而提高性能。
可重入锁。是指同一个线程可以多次获取同一把锁,而不会发生死锁。
在Java中,synchronized关键字和ReentrantLock类实现了可重入锁的机制。
可重入锁的实现方式是在锁状态中维护一个持有锁的线程和一个计数器,计数器记录了线程重入锁的次数。
在获取锁时,如果当前线程已经持有锁,则计数器加1,并直接返回。
在释放锁时,如果计数器不为0,则计数器减1,否则锁释放。
不可重入锁。是指同一个线程不能多次获得同一个锁,否则会导致死锁。
在Java中,JDK8里面提供的读写锁StampedLock类实现了可重入锁的机制。
可中断锁。是指允许被等待的线程在等待过程中可以被中断,从而提高程序的可靠性。
在Java中,ReentrantLock和ReentrantReadWriteLock都提供了可中断锁的实现方式。
ReentrantLock可中断锁的实现方式:使用lockInterruptibly()方法代替lock()方法,
这样在等待锁时可以响应中断请求。如果等待期间发生了中断,线程会立即退出等待,从而避免了无限等待的情况。