1.分类简介
编程中其实有很多锁的概念,不仅仅是 java ,而这些锁的概念是根据不同的性质分类而得到的。
2.详解
2.1 悲观锁和乐观锁
悲观锁和乐观锁是一种广义概念,体现的是看待线程同步的不同角度。
- 悲观锁
概念:悲观锁认为自己在使用数据的时候一定有别的 线程 来修改数据,在获取数据的时候会先加锁,确保数据不会被别的线程修改。
锁实现:关键字 synchronized 、接口Lock的实现类
适用场景:写操作较多,先加锁可以保证写操作时数据正确
- 乐观锁
概念: 乐观锁 认为自己使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据
锁实现:CAS算法,例如ActomicInteger类的原子自增是通过CAS自选实现
适用场景:读操作较多,不加锁的特点能够使其读操作的性能大幅提升
2.2 自旋锁
描述:是指当一个线程在获得锁的时候,如果锁一旦被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,自旋知道获取到锁才会提出循环。
意义:自旋锁不会使线程状态发生切换。不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快
缺点:如果某个线程持有锁的时间过长,就会导致其它等待获取锁的线程进入循环等待,消耗 CPU 。使用不当会造成 CPU使用率 极高。
自旋锁和CAS的关系是什么? CAS是实现自旋锁的基础。基本CAS操作+循环就可以实现自旋锁。
自旋锁一般不需要自己写,因为有很多前人写了比较有名的自旋锁。
例如:CLHLock——CLH锁是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋,获得锁。
JAVA并发包的AQS的设计是对CLH锁进行了优化或者说变体。
2.3 读写锁 (读锁/写锁)
描述:synchronized和RentrantLock都是排他锁,这些锁在同一时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。
锁实现:ReentrantReadWriteLock
使用ReentrantReadWriteLock实现一个并发环境下安全的缓存类示例:
上述示例中, Cache 组合一个非线程安全的 HashMap 作为缓存的实现,同时使用读写锁的读锁和写锁来保证Cache是线程安全的。在读操作get(String key)方法中,需要获取读锁,这使得并发访问该方法时不会被阻塞。写操作put( String key, Object value)方法和clear()方法,在更新HashMap时必须提前获取写锁,其他读写操作才能继续。Cache使用读写锁提升读操作的并发性,也保证每次写操作对所有的读写操作的可见性,同时简化了编程方式。