1. 锁的分类
Java中目前存在几大类型的锁
- 公平锁/非公平锁:是否按照访问的顺序获取锁。
- 共享锁/独占锁:共享锁可允许多个线程持有,例如读写锁中的读锁。独占锁只允许一个线程占有。
- 可重入锁/不可重入锁:针对同一个线程,是否能重复加锁。在函数互调的时候容易出现。
- 乐观锁/悲观锁:乐观锁的执行流程是:操作前不会加锁,在更新的时候通过版本号和cas来判断是否能执行,适合读多写少的的情况。例如mysql的mvvc。
悲观锁的执行流程是:操作前必须先获取到锁然后才能进行操作。
- 条件锁/读写锁:Lock.condition,需要满足条件(sinal)才能解锁。读写锁:Lock下实现的,其中有读锁和写锁(读读可以,其他只允许一个线程执行),乐观锁。
Ps: cas是通过while循环不断的自旋尝试修改,比较适合锁的时间比较短的场景。
2. 锁的理解
比较通俗的理解就是,锁是相当于临界区的钥匙,当然有不同的锁,不同的钥匙。
3. Lock和Synchronized的区别
- Lock是接口,是JDK层面的。Synchronized是Java关键字,是Jvm层面的。
- Synchronized可以实现自动释放锁,有完善的解锁机制。Lock必须手动解锁。
- Lock提供了接口支持锁的状态判断,Synchronized不支持。
- Synchronized是独占锁,无法获取锁的线程会堵塞。Lock中可以通过tryLock判断是否有线程占用了锁,可以防止堵塞
- Synchronized的底层(字节码)是通过monitorenter和monitorexit实现加锁和解锁。Lock底层则是通过AQS和CAS实现的。
- Synchronized是非公平,独占,可重入的,不可中断。 Lock是可公平/非公平,可重入,可中断的。
Ps: AQS=队列+CAS
4. 锁在Java的数据结构中的运用
1. hashtable: 所有函数都是加了Synchronized的,因此是线程安全的,没啥好分析的,性能当然也比较差。
2. Vector:和ArrayList不同,是线程安全的。也是通过Synchronized实现。
3. CopyOnWriteArrayList(CopyOnWriteArraySet):读不需要加锁(通过Array.CopyOf直接改变内部map的引用,浪费内存),写是通过ReentrantLock加锁实现的线程安全。在读多写少的场景可以大大提升效率。
4. ConcurrentLinkedQueue:高效的并发队列,源码解析将在后续整理。通过cas和"wait-free"算法实现
5. ConcurrentHashMap:不同于hashtable的加锁方式,在jdk1.7的时候是采用的分段锁的方式大大减少了锁竞争,一个segment里面有一个hash表。采用链表解决hash冲突。在jdk1.8以后采用cas+Synchronized锁住node的形式(看看guava的缓存实现方式),同时在同一个node下冲突的节点数量超过阈值则会转化成红黑树,减少查找的时间复杂度。
Ps: ConcurrentHashMap这种解决并发的算法和思想很值得在工作中借鉴和运用,也是用来考研一个人对于并发的理解深度。
====================================to be continue========================================