1. 悲观锁:面对共享数据,每次线程操作时都会上锁,其他线程阻塞。
Java中的 synchronized
和 ReentrantLock
等就是典型的悲观锁,还有一些使用了 synchronized 关键字的容器类如 HashTable
等也是悲观锁的应用。
2. 乐观锁:操作共享数据时不会上锁,会判断一下在此期间是否有其他线程操作此数据。
乐观锁可以使用版本号机制
和CAS算法
实现。在 Java 语言中 java.util.concurrent.atomic
包下的原子类就是使用CAS 乐观锁实现的
3. 独占锁:一次只被一个线程持有,其他的全阻塞,此线程既可以读也可以写。
JDK中的synchronized
和java.util.concurrent(JUC)
包中Lock的实现类就是独占锁。
4. 共享锁:多个线程持有,若其中一个线程给共享数据加上了共享锁,则其他线程只能读。
在 JDK 中 ReentrantReadWriteLock
就是一种共享锁。
5. 互斥锁:唯一+排他,某一资源同时只允许一个访问者对其进行访问
6. 读写锁: 相比于互斥锁,并发性更高,每次只有一个写线程,但有多个读线程。
写锁:独占锁,高优先级
读锁:共享锁,低优先级
读写的接口为:ReadWriteLock接口
public interface ReadWriteLock {
/**
* 获取读锁
*/
Lock readLock();
/**
* 获取写锁
*/
Lock writeLock();
}
7. 公平锁: 队列,先入先出,排队
true为公平锁,默认非公平
8. 非公平锁:
多个线程并不按照申请锁的顺序,高并发环境下,可能优先级反转或者某线程饥饿
在 java 中 synchronized 关键字是非公平锁,ReentrantLock默认也是非公平锁
9. 可重入锁:(递归锁)
指某一个线程,在外层方法获取了锁以后,在进入内层方法时会自动获取锁。
在 java 中 synchronized ,ReentrantLock也是可重入锁。
优势:避免死锁。
举例:下面这段代码,可重入锁的意思是,一旦某线程获取了A的锁就不同再获取B的锁了,如果不是可重入锁,可能线程1拿到了A的锁,却没有B的锁,B可能也需要A的锁,造成死锁现象(死锁:互斥使用、不可抢占、请求和保持、循环等待)。
public synchronized void mehtodA() throws Exception{
mehtodB();
}
public synchronized void mehtodB() throws Exception{
}
10. 自旋锁:
线程没有获得锁时自动进行循环,这种会浪费一些资源,目的是为了减少线程被挂起的概率,JDK1.6以后引入了自适应自旋锁。
11. 分段锁:
将锁的颗粒度细化。
锁优化
1. 锁:当多线程操作共享资源时会有“竞态”,为了让这些线程有着先后顺序。
2. Java锁:
*显式锁(Reentrantlock):必须手动申请lock()和释放unlock()
——可中断:对于内置锁,线程拿不到内置锁就会一直等待,除了获取锁没有其他办法能够让其结束等待。
——可定时:如果线程在指定的时间内没有获得锁,该方法就会返回false并结束线程等待。
——多条件队列:synchronized对应的只有一个条件队列,而ReentrantLock可以有多个条件队列。使用同一把锁的不同的线程可能有不同的条件谓词,如果只有一个条件队列,当某个条件谓词满足时就无法判断该唤醒条件队列里的哪一个线程;但是如果每个条件谓词都有一个单独的条件队列,当某个条件满足时我们就知道应该唤醒对应队列上的线程(内置锁通过Object.notify()
或者Object.notifyAll()
方法唤醒,显式锁通过Condition.signal()
或者Condition.signalAll()
方法唤醒)。这就是多个条件队列的好处。
*隐式锁、内置锁、内部锁(Synchronized):JVM管理
3. 锁优化(Java系统自身完成):
1)锁消除:JIT 编译器对内部锁的优化
逃逸分析:是Java针对逃逸对象(定义在方法内的对象,还被方法体外的其他变量引用了,在方法执行完毕时无法GC回收)的分析。
Java内部通过“逃逸分析”去分析加锁的代码段,看看是否被对个线程使用,若仅被这一个线程用,编译时就不产生 Synchronized 关键字,仅仅生成代码对应的机器码。
2)锁粗化:避免一个线程反复申请释放锁
3)偏向锁:偏向于第一个拿到这个锁的线程,如果在接下来的运行中,该锁没有被其他线程访问,就不会触发同步。为了避免相同线程在获取同一个锁是产生线程切换和同步操作。
4)适应锁:在申请锁时,这个锁正在被其他线程所使用,这个线程会进入(暂停(上下文切换消耗资源,适合线程处理时间长的)或忙等(while循环检查所是否被释放)状态)。
4.锁优化(手写代码):
1)减少临界区范围:减少锁持有时间
2)减少锁的颗粒度:降低锁的申请频率
3)读写锁:原本是一把操作共享资源的锁,分为读锁+写锁。
线程池优化
任务队列:多位乘客
线程池:多位司机,每个接走一个乘客
如何优化:
每个线程有:线程等待时间+线程CPU时间
线程等待时间所占比例越高,需要越多线程(IO操作)。线程 CPU 时间所占比例越高,需要越少线程(CPU密集型)。