锁是一种控制多线程访问共享资源的方式,Java程序有两种方式实现锁,第一种是依靠synchronized关键字,第二种是通过Lock接口及其实现类,“锁”的本质其实是monitorenter和monitorexit字节码指令的一个Reference类型的参数,即要锁定和解锁的对象。
可重入锁、不可重入锁
可重入锁指的是当一个线程获取到一个锁后,如果再次获取这个锁的话,依然可以获取到。
假如Synchronized不支持重入,进入method2方法时当前线程获得锁,method2方法里面执行method1时当前线程又要去尝试获取锁,这时如果不支持重入,它就要等释放,把自己阻塞,导致自己锁死自己。
不可重入锁,指的是当一个线程获取到一个锁后,如果再次获取这个锁的话,就不能再获取到了。不可重入锁会导致死锁,所以我们一般是不会使用不可重入锁的。
公平锁、非公平锁
AQS框架可以实现公平锁和非公平锁。AQS框架中包含三个主要元素,一个是状态status,用来表示表示锁是否被占用;另一个是当前线程;最后一个是队列,用来保存被阻塞的线程。
如果用来保存被阻塞线程的队列是按顺序获取锁的,队首线程先获取锁,那么这就是公平锁,每个线程都能够按照进入队列的顺序获取到锁。
如果用来保存被阻塞线程的队列不是按照顺序来获取锁的,而是大家一起竞争锁,那么就是非公平锁,这样可能会导致某些线程一直获取不到锁而被饿死。
悲观锁、乐观锁
悲观锁和乐观锁也是一种不同维度的锁的思想。
悲观锁指的是在执行操作之前加锁,来保证操作只有获取到锁的线程才能执行。这样的话,由于有加锁操作,所以效率可能会低,但是保证安全。比如synchronized和ReentrantLock都是悲观锁。
乐观锁在执行之前不加锁,而在执行的时候进行判断,比如CAS就是典型的乐观锁。执行的时候先比较线程工作内存是否为最新数据,如果是最新数据才进行更新,如果不是最新数据,工作线程内存中的数据已经过期,则交给开发者去处理。
自旋锁
自旋锁是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务,使用这种锁会造成busy-waiting。
独占锁、共享锁
独占锁、共享锁也是思想成本的概念。
独占锁指的是只有一个线程可以拥有这个锁来执行相应操作。比如:synchronized和ReentrantLock,以及读写锁中的写锁。
共享锁指的是可以有多个线程同时拥有这个锁。比如:读写锁中的读锁,是允许多个线程一同获取到的。还有Semaphore(信号量),因为允许多个线程通过,所以也是共享锁。
偏向锁
通俗的讲,偏向锁就是在运行过程中,对象的锁偏向某个线程。即在开启偏向锁机制的情况下,某个线程获得锁,当该线程下次再想要获得锁时,不需要再获得锁(即忽略synchronized关键词),直接就可以执行同步代码,比较适合竞争较少的情况。
轻量级锁
轻量级锁不是用来替代传统的重量级锁的,而是在没有多线程竞争的情况下,使用轻量级锁能够减少性能消耗,但是当多个线程同时竞争锁时,轻量级锁会膨胀为重量级锁。
重量级锁
即当有其他线程占用锁时,当前线程会进入阻塞状态。