Java锁相关知识
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========================================
上一篇: .net core xss攻击防御的方法