Java中的各种锁
一个线程中的多个流程能不能获取同一把锁:可重入锁和非可重入锁
- 可重入锁
可重入性:表明了锁的分配机制,是基于线程的分配,不是基于方法调用的分配。
是指在一个线程中可以多次获取同一把锁,避免了死锁
。
实现原理:为每个锁关联一个请求计数器和一个占有它的线程。计数器为0时,锁是未被占有的;线程占有锁时,计数器为1;之后线程继续再次请求时,计数器递增。当占用线程退出同步块,计数器递减,当计数器为0时,锁被释放。
例子:在一个线程中执行一个带锁的方法,在该方法中又调用了另一个需要相同锁的方法,线程可以直接执行调用的方法,不需要重新获得锁。
- ReentrantLock和synchronized都是可重入锁。
不可重入锁
:会出现死锁。
不能多次获取同一把锁。
多个线程竞争锁时要不要排队:公平锁和非公平锁
- 公平锁:
排队
以请求锁的顺序来获取锁,有多个线程在等待一个锁时,等待时间最久的线程会获得锁。
通过同步队列来实现多个线程按照申请锁的顺序来获取锁。
非公平锁
:先尝试插队,插队失败再排队。即多线程在获取锁时直接尝试获取锁,获取不到时才会到等待队列的队尾等待
。
无法保证锁的获取是按照请求锁的顺序进行的。
synchronized是非公平锁。
ReentrantLock和ReentrantReadWriteLock默认情况下是非公平锁,可以设置为公平锁。在构造两者的对象时,在构造函数中传入的是true则为公平锁,false时则为非公平锁。
线程获取锁失败,线程要不要阻塞?
获取锁失败时,线程不进行阻塞时,分为自旋锁
和适应性自旋锁
。
- 自旋锁
用于线程之间的同步,是一种非阻塞锁。
在线程没有获取到锁时不进入阻塞状态,不放弃CPU时间片,在原地忙等,一直循环着,线程反复检查锁变量是否可以用,等待获取到锁的线程释放锁。线程在这一状态保持执行,是一种忙等待
。
避免了线程上下文的调度切换的开销,单核cpu不适合自旋锁。
适用于锁使用者保持锁时间比较短的情况。自旋等待的时间必须要有一定的限度,如果自旋超过了限定次数还没有成功获得锁,就会挂起线程。
可能会产生死锁:同一线程两次调用lock(),会使得第二次调用lock的位置进行自旋,产生死锁。
- 递归程序不能再持有自旋锁时调用自己,也不能在递归调用时试图去获取相同的自旋锁。
自旋锁的实现原理同样也是CAS,在AtomicInteger中调用Unsafe进行自增操作时的while循环就是一个自旋操作,如果修改数值失败就通过循环来执行自旋,直到修改成功。
适应性自旋锁
自旋的时间(次数)不再固定,由前一次在同一个锁上的自旋时间以及锁的拥有着状态
来决定自旋次数。
线程要不要锁住同步资源:乐观锁和悲观锁
乐观锁
不锁住同步资源。
对于同一个数据的并发操作,乐观锁认为自己在使用数据时不会有别的线程修改数据,所有不会添加锁,只是在更新数据的时候再去判断之前有没有别的线程更新了这个数据
。
如果数据没有被更新,当前线程成功写入自己的数据;数据如果已经被其他线程修改,根据不同的实现方法执行不同的操作:报错或者自动重试。
乐观锁可以做到不锁定同步资源也可以正确的实现线程同步。
乐观锁在Java中的实现:通过无锁编程
来实现,最常采用CAS算法。
乐观锁适合读操作多的场景
,不加锁的特点使读操作的性能提升很多。
悲观锁
锁住同步资源。
对于同一个数据的并发操作,悲观锁认为自己在使用数据时一定有线程来修改数据,因此在获取数据时会先加锁,以确保数据不会被别的线程所修改。
synchronized和Lock都是悲观锁。
悲观锁适合写操作多的场景
,先加上锁可以保证写操作时数据的正确性。
多个线程竞争同步资源的流程细节有没有区别?
无锁、偏向锁、轻量级锁、重量级锁这四种锁是指锁的状态,专门针对synchronized的
。
无锁
不锁住资源,多个线程中只有一个能修改资源成功,其他线程会重试。
偏向锁
同一个线程执行同步资源时自动获取资源。
轻量级锁
多个线程竞争同步资源时,没有获取资源的线程自旋等待锁释放。
重量级锁
多个线程竞争同步资源时,没有获取资源的线程阻塞等待唤醒。
多个线程能不能共享一把锁:共享锁和排他锁(独享锁)
共享锁
锁可以被多个线程所持有。
一个线程对一个对象加上共享锁后,其他线程只能对这个对象再加共享锁,不能再加排他锁。
获得共享锁的线程只能读数据,不能写、修改数据。
11.排他锁(独享锁、互斥锁)
锁一次只能被一个线程所拥有。
一个线程对一个对象加上排他锁后,其他线程不能再对对象加任何类型的锁。
获得排他锁的线程既可以读数据又可以写数据。
ReentrantReadWriteLock中有两把锁:ReadLock(读锁)和WriteLock(写锁),合称为读写锁,读锁是共享锁,写锁是排他锁
。
读写锁将对一个资源文件的访问分为了读锁和写锁。
读锁使得多个线程之间的读操作不会发生冲突,读锁的共享锁可以保证很高效的并发读。
其他的锁
- 可中断锁
可以相应中断的锁。
- synchronized是不可中断的锁,Lock是可以中断的锁。
线程在等待获取锁时等待的时间过长,可以中断自己或在别的线程中中断它去处理其他的事情。
- 互斥锁
Java内置锁称为互斥锁。没有获取到锁的线程直接进入阻塞状态。
执行流程:托管代码-->用户态代码-->内核态代码。
采用互斥的方式,从没有获得锁到获得锁的过程中,要有用户态和内核态调度和上下文切换的开销和损耗。