目录
1.乐观锁
2.悲观锁
3.自旋锁
1.自旋锁的概念
2.自旋等待时间
3.自旋锁的优缺点
4.自旋周期的选择
4.ReentrantLock
1.定义
2.主要方法
3.非公平锁与公平锁
1.非公平锁
2.公平锁
5.Condition类和Object类锁的方法
6.tryLock()、lock()、lockInterruptibly的区别
7.Semaphore信号量
8.Semaphore和ReentrantLock的联系
9.可重入锁(递归锁)
10.ReadWriteLock读写锁
乐观锁认为读多写少,遇到并发写的可能性低,因此操作数据的时候不会上锁。在更新的时候会判断一下在此期间是否更新过数据,未更改过才进行加锁操作,如果失败则重复读-比较-写的操作。
悲观锁认为写多读少,遇到并发的可能性高,因此每次读写数据时都会上锁,其它线程要读写该数据会先阻塞直到获得锁。synchronizd就是一个典型的悲观锁
如果持有锁的线程能在很短的时间内释放锁资源,则等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,只需要等一等(即自旋),等持有锁的线程释放锁后可以立即获得锁,从而避免了用户线程和内核的切换产生的资源消耗。
线程的自旋需要消耗CPU,所以需要设置一个自旋等待的最大时间,若超过自旋等待最大时间没有释放锁,则将该线程停止自旋进入阻塞状态
优点:能减少线程的阻塞,且不需要切换用户态与内核态从而提升了性能
缺点:若大量线程竞争一个锁,则线程自旋的消耗会大于阻塞挂起的消耗,此时需要关闭自旋锁。
jdk1.5将自选周期写为固定的,而jdk1.6引入了适应性自旋锁,由前一次在同一个锁上的自旋时间和锁的拥有者的状态来决定,选择一个最佳的自选周期。
ReentrantLock实现了接口Lock,是一种可重入锁。
void lock():若锁处于空闲状态,当前线程将获取锁;若锁已经被其他线程持有,将禁用当前线程,直到当前线程获得锁
void unlock():执行此方法,当前线程将释放持有的锁
boolean tryLock():如果锁可用,则获取锁,并返回true;否则返回false,但该线程不会被禁用
void lockInterruptibly():如果当前线程未被中断,获取锁
JVM按随机、就近原则分配锁的机制
锁的分配机制是公平的,通常先对锁提出获取请求的线程会被分配到锁
非公平锁的执行效率远高于公平锁,因此ReentrantLock在构造函数中默认锁的初始化方式为非公平锁
1.Condition类的await()方法等效于Object类中的wait()方法
2.Condition类的signal()方法等效于Object类中的notify()方法
3.Condition类的signalAll()方法等效于Object类中的notifyAll()方法
tryLock():能获得锁就返回true,不能就立即返回false,且无法获得锁也不会禁用当前线程
lock:能获得锁就返回true,不能就一直等待锁
lockInterruptibly:若执行该方法时中断了这个线程,会抛出异常
1.Semaphore信号量是一种基于计数的信号量。
2.作用:可以设定一个阈值,阈值范围内的多个线程竞争获取许可信号,完成后归还;
超过阈值后,线程申请许可信号将会被阻塞。
3.通常用于数据库连接池中构建对象池、资源池。
// 创建一个计数阈值为 3 的信号量对象
// 只能 3 个线程同时访问
Semaphore semp = new Semaphore(3);
try {
// 申请许可
semp.acquire();
try {
System.out.println("线程1,2,3,4,5");
} catch (Exception e) {
e.printStackTrace();
System.out.println("异常已处理");
} finally {
// 释放许可
semp.release();
}
} catch (InterruptedException e) {
}
1.Semaphore基本能实现ReentrantLock的所有功能,使用方法类类似,通过调用acquire()和release()获得和释放临界资源。
2.Semaphore中的锁释放也由手动进行,与ReentrantLock一样,释放锁的操作必须在finally中完成
3.Semaphore也提供了公平锁与非公平锁机制
1.可重入锁,也叫做递归锁,指同一线程外层函数获得锁后,内层递归函数仍然有获取该锁的代码,即同一个线程可无限次地进入同一把锁的不同代码。
2.Java中的ReentrantLock和synchronized都是可重入锁。
代码示例:同一个类中的synchronize关键字修饰了不同的方法,synchronize是可重入锁,而代码中的两个方法使用的是同一把锁,只要能执行showB()即线程占用了锁,所以执行showA()方法就不用被阻塞等待获取锁了;如果不是同一把锁或非可重入锁,就会在执行showA()时被阻塞等待。
class Show extends Thread{
private synchronized void showA(){
System.out.println("同步方法A");
}
private synchronized void showB(){
System.out.println("同步方法B");
}
}
读写锁分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,由jvm控制
读锁:代码只读数据,并且可供多个线程同时读,需要使用读锁
写锁:代码修改数据,并且只能一个线程修改,需要使用写锁