Lock接口以及对象,控制了竞争资源的安全访问,但是这种锁不区分读写,称这种锁为普通锁。为了提高性能,Java提供了读写锁,在读的地方使用读锁,在写的地方使用写锁,灵活控制,如果没有写锁的情况下,读是无阻塞的,在一定程度上提高了程序的执行效率。
互斥锁(ReentrantLock)
概念
是一次最多只能有一个线程持有的锁。
当有一个线程要访问共享资源(临界资源)之前会对线程访问的这段代码(临界区)进行加锁。
如果在加锁之后没释放锁之前其他线程要对临界资源进行访问,则这些线程会被阻塞睡眠,直到解锁,
如果解锁时有一个或者多个线程阻塞,那么这些锁上的线程就会变成就绪状态,
然后第一个变为就绪状态的线程就会获取资源的使用权,并且再次加锁,其他线程继续阻塞等待。
实例
在jdk1.5之前, 我们通常使用synchronized机制控制多个线程对共享资源的访问。
Lock提供了比synchronized机制更广泛的锁定操作
互斥锁在Java中的具体实现就是ReentrantLock
Lock和synchronized机制的主要区别
synchronized机制提供了对与每个对象相关的隐式监视器锁的访问,并强制所有锁获取和释放均要出现在一个块结构中,当获取了多个锁时, 它们必须以相反的顺序释放。synchronized机制对锁的释放是隐式的,只要线程运行的代码超出了synchronized语句块范围,锁就会被释放。而Lock机制必须显式的调用Lock对象的unlock()方法才能释放锁,这为获取锁和释放锁不出现在同一个块结构中,以及以更自由的顺序释放锁提供了可能。
读写锁(ReentrantReadWriteLock)
概念
共享互斥锁,读模式共享,写模式互斥
它有三种模式:读加锁状态,写加锁状态和不加锁状态。
实现方式
允许一次读取多个线程,但一次只能写入一个线程
读锁-读加锁状态
读加锁的模式下,任何线程都可以对其进行读加锁的操作
但所有试图进行写加锁操作的线程都会被阻塞。
直到所有读线程解锁。
如果没有线程锁定ReadWriteLock进行写入,则多线程可以访问读锁。
但是当读线程太多时,写线程一直被阻塞显然是不对的,所以一个线程想要对其进行写加锁时,就会阻塞读加锁,先让写加锁线程加锁
写锁-写加锁状态
写加锁的模式下,任何线程对其进行加锁操作都会被阻塞,直到解锁。
如果没有线程正在读或写,那么一个线程可以访问写锁。
其中:
读取锁允许多个reader线程同时持有,而写入锁最多只能有一个writer线程持有。
不加锁状态
获取锁的方法
可以调用写入锁的newCondition()方法获取与该写入锁绑定的Condition对象,此时与普通的互斥锁并没有什么区别,但是调用读取锁的newCondition()方法将抛出异常。
源码
低16位代表写锁,高16位代表读锁
使用场景
读写锁的使用场合是:读取数据的频率远大于修改共享数据的频率。在上述场合下使用读写锁控制共享资源的访问,可以提高并发性能。
读写锁适合于对数据结构的读次数比写次数多得多的情况. 因为, 读模式锁定时可以共享, 以写模式锁住时意味着独占, 所以读写锁又叫共享-独占锁.
读写锁特点:
1)多个读者可以同时进行读
2)写者必须互斥(只允许一个写者写,也不能读者写者同时进行)
3)写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)、
如果一个线程已经持有了写入锁,则可以再持有读锁。相反,如果一个线程已经持有了读取锁,则在释放该读取锁之前,不能再持有写入锁。
如果解锁时有一个以上的线程阻塞,那么所有该锁上的线程都被编程就绪状态, 第一个变为就绪状态的线程又执行加锁操作,那么其他的线程又会进入等待。 在这种方式下,只有一个线程能够访问被互斥锁保护的资源