1.死锁:死锁是指两个或两个以上的进程或线程在执行过程中,由于彼此竞争资源而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程
2.活锁:活锁是指多个进程或线程面临空闲的共享资源时都不会选择主动使用的一种现象,这会导致彼此之间不可继续运行进行下去(线程1可以使用资源,但它很礼貌,让其他线程先使用资源,线程2也可以使用资源,但它很绅士,也让其他线程先使用资源。这样你让我,我让你,最后两个线程都无法使用资源,造成的一种循环现象)
3.可重入锁:可重入锁是指在一个线程已经获得了某把锁的前提下,该线程可以再次获取同一把锁而且不会出现死锁的现象,这样的锁称为可重入锁(ReentrantLock和Synchronized)->可递归调用的一把锁
4.不可重入锁:不可重入锁是指在一个线程已经获得了某把锁的前提下,该线程不可以再次获取同一把锁,否则会出现死锁的现象,这样的锁称为不可重入锁->不可递归调用的一把锁
************************************************************************************************************************************
5.偏向锁:偏向锁的目的是在某个线程获得锁之后,消除这个线程锁重入(CAS)的开销,看起来让这个线程得到了偏护。引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令。偏向锁适用于无竞争的场景,在只有一个线程执行同步块时可以进一步提高性能 ,轻量级锁是为了在线程交替执行同步块时提高性能。如果某个线程要执行同步代码,就尝试使用CAS修改ThreadID,修改成功就执行同步代码,不成功就将偏向锁升级成轻量锁
6.轻量级锁:获取轻量锁的过程与偏向锁不同,竞争锁的线程首先需要拷贝对象头中的Mark Word到栈帧的锁记录中。拷贝成功后使用CAS操作尝试将对象的Mark Word更新为指向当前锁记录的指针。如果这个更新动作成功了,那么这个线程就拥有了锁。如果更新失败,那么意味着有多个线程在竞争,轻量级锁就会膨胀为重量级锁。轻量级锁适应的场景是线程交替执行同步块的情况,如果存在同一时间多个线程竞争同一把锁的情况时,就会导致轻量级锁膨胀为重量级锁
7.重量级锁:当多线程竞争重量级锁竞争失败后线程会被阻塞直至锁被释放后唤醒阻塞的线程继续竞争。重量级锁适用于同步代码块执行时间长、锁竞争激烈的情况
总结: 随着对锁竞争的加强,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)。偏向锁适用于无竞争的场景,轻量级锁适用于线程交替执行同步代码块的情况,如果存在同一时间竞争同一锁的情况,就会导致轻量级锁膨胀为重量级锁
************************************************************************************************************************************
8.乐观锁:乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去获取数据的时候都认为其他线程不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间其它线程有没有去更新这个共享资源,另外对于ABA问题可以使用带有版本号机制的CAS来避免。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Java中java.util.concurrent.atomic包下面的原子类就是借鉴了乐观锁思想的一种CAS实现
9.悲观锁:悲观锁是就是一种悲观思想,即认为写多读少,遇到并发写的可能性高,每次去获取数据的时候都认为其他线程会修改,所以每次在读写数据的时候都会上锁,这样其它想读写共享资源的线程就会阻塞直到获得锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现
10.自旋锁:自旋锁的思想非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可快速竞争锁,这样就可以避免线程在内核态和用户态之间切换的消耗。线程自旋是需要消耗CPU的,也就是让CPU做无用功,如果一直获取不到锁,那线程也不能一直占用 CPU自旋做无用功,所以需要设定一个自旋等待的最大时间。如果持有锁的线程执行时间超过自旋等待的最大时间后仍没有释放锁,就会导致其它争用锁的线程停止自旋进入阻塞状态。
缺点:(1) 如果多个线程对于锁的竞争非常激烈,或者持有锁的线程需要长时间占用锁执行同步块,这时候就不适合使用自旋锁。这是因为如果同时有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,而且其它需要 CPU 的线程又不能获取到 CPU ,造成 CPU 资源的浪费,所以这种情况下我们要关闭自旋锁 。(2) 如果实现的自旋锁是不公平的,即无法满足等待时间最长的线程优先获取锁,就会存在“线程饥饿”问题
优点:(1) 自旋锁会尽可能的减少线程的阻塞,这对于锁资源竞争不激烈且占用锁资源时间非常短的代码块来说性能会得到大幅度的提升—因为自旋的消耗会小于线程阻塞挂起再唤醒的操作的消耗,这些操作会导致线程发生两次上下文切换
public class SpinLock {
//指向Thread变量的原子引用cas
private AtomicReference<Thread> cas = new AtomicReference<Thread>();
public void lock() {
//获取当前线程
Thread current = Thread.currentThread();
// 如果当前已经有其它线程获取到锁,则当前线程进行循环等待
while (!cas.compareAndSet(null, current)) {
// DO nothing(什么也不做)
}
}
public void unlock() {
Thread current = Thread.currentThread();
cas.compareAndSet(current, null);
}
}
假设有两个线程,当第一个线程A成功获取锁后并且还没有释放锁之前,如果另一个线程B又来尝试获取锁,此时由于满足while条件,线程B就会进入while循环,不断判断是否满足CAS条件,直到A线程调用unlock方法释放了该锁,线程B才可获取锁并且继续向下执行
11.公平锁:多个线程会按照申请公平锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一个线程才能得到锁
优点:所有的线程都能获得锁资源,不会饿死在队列中
缺点:吞吐量会下降很多,除了队列里面的第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大
12.非公平锁:多个线程如果想要获取非公平锁,会直接尝试去获取不必进入等待队列,获取不到时,再进入等待队列,如果能获取到,就直接获取到锁
优点:可以减少唤醒线程的开销,整体的吞吐效率会高点
缺点:导致队列中的某些线程会一直获取不到锁或者长时间获取不到锁,产生 饥饿 现象