Java中的锁
1.简介
JAVA中实现加锁是通过Synchronized关键字以及java.util.concurrent包下的相关类。
2.java.util.concurrent包
Lock接口
//试图获取锁.
void lock()
//如果当前线程未被中断,则获取锁.
void lockInterruptibly()
//返回绑定到此 Lock 实例的新 Condition 实例.
Condition newCondition()
//仅在调用时锁为空闲状态才获取该锁.
boolean tryLock()
//如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁.
boolean tryLock(long time, TimeUnit unit)
//试图释放锁.
void unlock()
ReentrantLock类
//创建一个 ReentrantLock 的实例.
ReentrantLock()
//创建一个具有给定公平策略的 ReentrantLock实例.
ReentrantLock(boolean fair)
公平锁:多线程按照申请锁的顺序获取锁。
非公平锁:多线程并非按照申请锁的顺序获取锁,即先申请锁的线程不一定第一个能获取锁。
//试图获取锁.
void lock()
//如果当前线程未被中断,则获取锁.
void lockInterruptibly()
//仅在调用时锁未被另一个线程保持的情况下,才获取该锁.
boolean tryLock()
//如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁.
boolean tryLock(long timeout, TimeUnit unit)
//试图释放此锁.
void unlock()
//返回用来与此 Lock 实例一起使用的 Condition 实例.
Condition newCondition()
//如果此锁的公平设置为 true,则返回 true.
boolean isFair()
//返回正等待获取此锁的线程估计数.
int getQueueLength()
//返回等待与此锁相关的给定条件的线程估计数.
int getWaitQueueLength(Condition condition)
//返回标识此锁及其锁定状态的字符串.
String toString()
*尽量在finally语句块中释放锁,那么当程序发生异常时也能进行释放。
Condition接口
//使当前线程在接收到信号前或被中断前一直处于等待状态.
void await()
//使当前线程在接收到信号前或被中断前或达到指定时间前一直保持等待状态(TimeUnit为时间单位).
boolean await(long time, TimeUnit unit)
//使当前线程在接收到信号前或被中断前或达到指定时间前一直保持等待状态(单位为毫秒).
long awaitNanos(long nanosTimeout)
//使当前线程在接收到信号前或被中断前或达到最后日期期限前一直保持等待状态.
boolean awaitUntil(Date deadline)
//唤醒一个在该Condition实例等待的线程.
void signal()
//唤醒所有在该Condition实例等待的线程.
void signalAll()
*一个Condition实例将与一个Lock实例进行绑定,作为该Lock实例的条件控制。
*Condition接口声明的方法调用前都需要先获取与此Condition相关的锁。
*await()、await(long time, TimeUnit unit)、awaitNanos(long nanosTimeout)、awaitUntil(Date deadline)方法执行后,将会阻塞线程等待被唤醒,同时与此Condition相关的锁将以原子方式释放。
*signal()、signalAll()方法调用后,将会唤醒在指定Condition中等待的线程,被唤醒的线程需要重新获取锁才能从await()方法进行返回。
/** * @Auther: ZHUANGHAOTANG * @Date: 2018/9/26 17:36 * @Description: */ public class TestReentranLock implements Runnable{ /** * 可重入锁 */ private ReentrantLock reentrantLock = new ReentrantLock(true); /** * 锁条件 */ private Condition condition = reentrantLock.newCondition(); /** * 业务处理 */ public void service(){ String threadName = Thread.currentThread().getName(); System.out.println(threadName+":尝试获取锁"); reentrantLock.lock(); System.out.println(threadName+":获取锁成功"); try { System.out.println(threadName+":使当前线程等待,并释放锁资源。"); condition.await(); } catch (InterruptedException e) { e.printStackTrace(); }finally { reentrantLock.unlock(); System.out.println(threadName+":释放锁"); } } /** * 唤醒在该Condition实例等待的线程 */ public void signalAll(){ reentrantLock.lock(); condition.signalAll(); reentrantLock.unlock(); } @Override public void run() { service(); } public static void main(String[] args) { TestReentranLock testReentranLock = new TestReentranLock(); Thread threadA = new Thread(testReentranLock,"线程A"); Thread threadB = new Thread(testReentranLock,"线程B"); Thread threadC = new Thread(testReentranLock,"线程C"); threadA.start(); threadB.start(); threadC.start(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } testReentranLock.signalAll(); } }
线程B:尝试获取锁
线程A:尝试获取锁
线程B:获取锁成功
线程C:尝试获取锁
线程B:使当前线程等待,并释放锁资源。
线程A:获取锁成功
线程A:使当前线程等待,并释放锁资源。
线程C:获取锁成功
线程C:使当前线程等待,并释放锁资源。
线程B:释放锁
线程C:释放锁
线程A:释放锁
线程B:尝试获取锁
线程A:尝试获取锁
线程A:获取锁成功
线程C:尝试获取锁
线程A:使当前线程等待,并释放锁资源。
线程C:获取锁成功
线程C:使当前线程等待,并释放锁资源。
线程B:获取锁成功
线程B:使当前线程等待,并释放锁资源。
线程A:释放锁
线程B:释放锁
线程C:释放锁
ReadWriteLock接口
//返回用于读取操作的锁.
Lock readLock()
//返回用于写入操作的锁.
Lock writeLock()
ReentrantReadWriteLock类
//创建一个ReentrantReadWriteLock实例.
ReentrantReadWriteLock()
//创建一个具有给定公平策略的ReentrantReadWriteLock实例.
ReentrantReadWriteLock(boolean fair)
//返回用于读取操作的锁.
Lock ReentrantReadWriteLock.ReadLock.readLock()
//返回用于写入操作的锁.
Lock ReentrantReadWriteLock.WriteLock.writeLock()
//返回等待获取读取或写入锁的线程估计数目.
int getQueueLength()
//如果此锁的公平设置为 true,则返回 true.
boolean isFair()
//返回标识此锁及其锁状态的字符串.
String toString()
ReadLock/WriteLock静态内部类
//试图获取锁.
void lock()
//如果当前线程未被中断,则获取锁.
void lockInterruptibly()
//返回绑定到此 Lock 实例的新 Condition 实例.
Condition newCondition()
//仅在调用时锁为空闲状态才获取该锁.
boolean tryLock()
//如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁.
boolean tryLock(long time, TimeUnit unit)
//试图释放锁.
void unlock()
//返回标识此锁及其锁状态的字符串.
String toString()
*因为ReadLock不支持条件,因此当调用了ReadLock的newCondition()方法时将会抛出UnsupportedOperationException异常。
*使用ReentrantReadWriteLock的读锁以及写锁,将会遵循读读共享、写写互斥、读写互斥。
/** * @Auther: ZHUANGHAOTANG * @Date: 2018/9/26 18:04 * @Description: */ public class TestReentrantReadWriteLock implements Runnable{ private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(true); /** * 读锁 */ private Lock readLock = reentrantReadWriteLock.readLock(); /** * 写锁 */ private Lock writeLock = reentrantReadWriteLock.writeLock(); /** * 读取操作 */ public void reading(){ String threadName = Thread.currentThread().getName(); System.out.println(threadName+":尝试获取读锁"); readLock.lock(); System.out.println(threadName+":获取读锁成功"); System.out.println(threadName+":释放读锁"); readLock.unlock(); } /** * 写入操作 */ public void writing(){ String threadName = Thread.currentThread().getName(); System.out.println(threadName+":尝试获取写锁"); writeLock.lock(); System.out.println(threadName+":获取写锁成功"); System.out.println(threadName+":释放写锁"); writeLock.unlock(); } public static void main(String[] args) { TestReentrantReadWriteLock testReentrantReadWriteLock = new TestReentrantReadWriteLock(); Thread threadA = new Thread(testReentrantReadWriteLock,"线程A"); Thread threadB = new Thread(testReentrantReadWriteLock,"线程B"); Thread threadC = new Thread(testReentrantReadWriteLock,"线程C"); threadA.start(); threadB.start(); threadC.start(); } }
@Override
public void run() {
//读读共享
reading();
}
线程A:尝试获取读锁
线程B:尝试获取读锁
线程A:获取读锁成功
线程B:获取读锁成功
线程A:释放读锁
线程B:释放读锁
线程C:尝试获取读锁
线程C:获取读锁成功
线程C:释放读锁
*读锁能被多个线程同时持有,能提高读取的效率 (虽然只用读锁时可以不进行释放,但会影响写锁的获取)
@Override
public void run() {
//写写互斥
writing();
}
线程A:尝试获取写锁
线程B:尝试获取写锁
线程A:获取写锁成功
线程C:尝试获取写锁
线程A:释放写锁
线程B:获取写锁成功
线程B:释放写锁
线程C:获取写锁成功
线程C:释放写锁
*写锁同一时刻只能被一个线程所持有。
@Override
public void run() {
//读写互斥
writing();
reading();
}
线程A:尝试获取写锁
线程C:尝试获取写锁
线程B:尝试获取写锁
线程A:获取写锁成功
线程A:释放写锁
线程A:尝试获取读锁
线程C:获取写锁成功
线程C:释放写锁
线程C:尝试获取读锁
线程B:获取写锁成功
线程B:释放写锁
线程B:尝试获取读锁
线程C:获取读锁成功
线程C:释放读锁
线程A:获取读锁成功
线程A:释放读锁
线程B:获取读锁成功
线程B:释放读锁
*读的时候不能写,写的时候不能读,即获取读锁时如果写锁此时被线程持有则将等待写锁被释放,获取写锁时如果读锁此时有被线程持有则将等待读锁被释放且写锁未被持有。
3.Java中锁的分类
公平锁/非公平锁
公平锁:多线程按照申请锁的顺序获取锁。
非公平锁:多线程并非按照申请锁的顺序获取锁,即并未第一个lock()的线程能第一个获取锁。
*对于ReentranLock以及ReentrantReadWriteLock锁其可以通过构造方法来设置是公平锁还是非公平锁。
*对于Synchronized关键字其属于非公平锁。
共享锁/独享锁
共享锁:指该锁能被多个线程同时持有,对于ReadLock其属于共享锁。
独享锁:指该锁同一时刻只能被一个线程持有,对于ReentranLock、WriteLock、Synchronized其属于独享锁。
锁/悲观锁
乐观锁和悲观锁并不是具体的锁特性,而是看待并发时的角度。
乐观锁:认为并发操作不会影响数据的完整性,因此无需进行加锁。
悲观锁:认为并发操作一定会影响数据的完整性,因此必须进行加锁。
*读操作适用于乐观锁,即不进行加锁,能够提升读取数据的效率。
*写操作适用于悲观锁,即一定要进行加锁。
分段锁
分段锁是指锁的设计,通过细化锁的粒度来控制并发的操作,在Java的ConcurrentHashMap中的segment就是通过分段锁的设计来实现并发操作。
偏向锁/轻量级锁/重量级锁
偏向锁、轻量级锁、重量级锁都是指锁的不同状态而且是针对Synchronized关键字而言的。
偏向锁:指同步方法或同步语句块一直只被一个线程所持有,则此时锁的状态为偏向锁,会降低该线程获取锁的成本。
轻量级锁:当锁的状态为偏向锁,若被其他线程访问,则此时其他线程将通过自旋的方式尝试获取锁,则此时锁的状态为轻量级锁。
重量级锁:当锁的状态是轻量级锁,若其他线程自旋过后仍未获取到锁,则此时锁的状态为重量级锁,此时其他线程将会进入阻塞状态,性能降低。
*自旋即通过循环一定次数的方式尝试获取锁,使用此方式不会阻塞线程,缺点是消耗CPU的性能。
*当锁的状态为偏向锁时性能最高,重量级锁时性能最低。