1、公平锁/非公平锁
2、可重入锁/不可重入锁
3、悲观锁/乐观锁
4、自旋锁
5、分段锁
6、互斥锁/读写锁
7、独享锁/共享锁
8、偏向锁/轻量级锁/重量级锁
以上锁分类并不全指锁的状态,有些是锁的特性、设计。
公平锁:按线程申请锁的先后顺序获得锁
非公平锁:线程不是按先后顺序获得锁,效率比公平锁效率高
可重入锁:已获得的锁在内层可重复使用
不可重入锁:已获得的锁在内层不可重复使用
使用一个自旋锁来模拟一个不可重入锁
public class UnreentrantLock {
private AtomicReference<Thread> owner = new AtomicReference<>();
public void lock() {
Thread current = Thread.currentThread();
for (; ; ) {
if (!owner.compareAndSet(null, current)) {
return;
}
}
}
public void unlock() {
Thread current = Thread.currentThread();
owner.compareAndSet(current, null);
}
}
悲观锁:每次都假定访问时会有多个线程,执行代码前都要加锁,加锁成功后,其它线程处于阻塞状态
乐观锁:其实就是无锁,CAS就是无锁,每次都假定访问时只有自己一个线程
自旋锁:当一个线程在获得锁的时候,有其它线程获得锁,那么该线程就循环判断是否能获得锁,直至其它线程释放锁,然后获得锁。
分段锁:它是一种锁的设计,ConcurrentHashMap就是将hash数据分成多段,持有多把锁,其实就是分成多个hashmap,只有当线程访问同一把锁时才会阻塞,提高了效率。
互斥锁:加锁的代码块只能允许一个线程访问,其它线程则会阻塞
读写锁:它的read模式是共享锁、write模式是互斥锁。读锁和读锁能共存,读锁和写锁、写锁和写锁均不能共存
独享锁:就是互斥锁,该锁每次只能有一个线程访问
共享锁:该锁可以有多个线程访问
偏向锁:同一代码块一直被同一线程获得锁,那么下次将自动获得锁,降低获得锁的代价
轻量级锁:当锁是偏向锁时,被其它线程访问,偏向锁就会升级为轻量级锁,通过自旋转来获取锁,不会阻塞,提高性能
重量级锁:当轻量级锁时,线程一直自旋,当自旋次数达到一定次数时,将提升为重量级锁,让其它申请锁的线程进入阻塞状态,降低cpu的负载。synchronized是重量级锁。
CAS是无锁的一种实现,自旋锁底层就是CAS,ReentrantLock是通过AQS来实现线程调度的。
synchronized底层实现原理:
对于synchronized关键字而言,javac在编译时,会生成对应的monitorenter和monitorexit指令分别对应于synchronized的进入和退出 ,有两个monitorexit的原因是保证抛出异常也能释放锁,所以javac为同步代码块添加一个隐式try-finally,在finally中会调用monitorexit命令释放锁。对于synchronized方法而言,javac为其生成一个ACCSYSCHRONIZED关键字,在JVM方法被ACCSYSCHRONIZED修饰时,会先尝试获得锁。这两种synchronized语义实现大致相同。
什么是AQS?
AQS(AbstractQueuedSynchronizer)使用一个int成员变量来表示 同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作,状态信息通过protected类型的getState,setState,compareAndSetState来进行操作
AQS支持独占式和共享式这两种同步方法,独占式如ReentrantLock,共享式如Semaphore,CountDownLatch,组合式如ReentrantReadWriteLock。
同步器的设计基于模板方法模式,使用方法如下:
1、继承AbstractQueuedSynchronizer并重写指定方法(重写方法simple,就是对共享资源state的获取和释放)
2、将AQS组合在自定义同步组件的实现中,并调用模板方法,这些模板方法会调用使用者重写的方法。
降低锁的竞争状态有三种方法:
1、降低锁持有的时间
2、降低获取锁的频率
3、使用带协调机制的独占锁