Java-并发编程-02并发包-01Lock体系


1 LockSupport

1.1 LockSupport定义

  • LockSupport使用Unsafe类实现的,在rt.jar包内提供的,主要作用是挂起和唤醒线程
  • 可以在线程内任意位置让线程阻塞
  • 与Thread.suspend()相比,弥补了resume()方法导致线程无法继续执行的情况
  • 与Object.wait()相比,不需要先获得某个对象的锁,也不会抛出InterruptedException异常
  • 不用担心unpark()方法在park()方法之前执行而导致park()方法永久挂起问题

1.2 LockSupport原理

  • LockSupport使用类似信号量机制,为每个线程准备了一个许可,如果许可可用,park()方法会立即返回,并且消费这个许可(许可变为不可用),如果许可不可用就会阻塞,unpark()方法会使得许可可用(和信号量不通的是许可不能累加,不能拥有超过一个许可,永远只有一个),使得unpark()方法操作发生在park()方法之前

1.3 LockSupport主要方法

  • LockSupport.park()
    • 如果调用park方法的线程已经拿到了与LockSupport关联的许可证,则调用LockSupport.park()时会马上返回,否则调用线程会被禁止参与线程的调度,即阻塞
    • 其他线程调用unpark(Thread thread)方法并将当前线程作为参数时,之前调用park方法而被阻塞的线程会返回
    • 如果其他线程调用了阻塞线程的interrupt()方法,设置了中断标志或者线程被虚假唤醒,则阻塞线程也会返回,所以调用park方法最好也使用循环条件判断方式
    • 因调用park方法而被阻塞的线程被其他线程中断而返回时并不会抛出InterruptedException异常
  • LockSupport.unpark(Thread.currentThread());
    • 当一个线程调用unpark时,如果参数thread线程没有持有thread与LockSupport类关联的许可证,则让thread持有.
    • 如果thread之前因调用park()而被挂起,则调用unpark后,该线程会被唤醒
    • 如果thread之前没有调用park,则调用unpark方法后,再调用park方法,其会立刻返回
  • LockSupport.parkNanos(long nanos)
    • 与park方法类似,不同之处是如果没有拿到许可证,则调用线程会被挂起nano时间后修改为自动返回

2 StampedLock

2.1 StampedLock定义

  • 是JDK8新增的锁,提供了三种读写控制,当调用获取锁系列的方法时,会返回long类的变量(stamp),代表了锁的状态. try系列获取锁的方法,当获取锁失败后会返回为0的stamp值. 当调用释放锁和转换锁的方法时需要传入获取锁时stamp值
  • StampedLock还支持三种锁(写锁,悲观读锁,乐观读锁)在一定条件下进行互相转换

2.2 写锁WriteLock

  • 是一个排他锁/独占锁,且不可重入
  • 请求该锁成功后会返回一个stamp变量用来表示该锁的版本,释放该锁时调用unlockWrite方法并传递获取锁时stamp参数.并提供tryWriteLock方法

2.3 悲观读锁ReadLock

  • 是一个共享锁,在没有现成获取独占锁的情况下,多个线程可以同时获取该锁.
  • 如果有线程持有写锁,则其他线程请求获取该读锁会被阻塞,写锁也不可重入
  • 请求该锁成功后会返回一个stamp变量用来表示该锁的版本,当释放该锁时需要调用unlockRead方法并传递stamp参数,并且提供了非阻塞tryReadLock方法
  • 适用于读少写多的场景

2.4 乐观读锁tryOptimisticRead

  • 是相对于悲观锁readLock来说的,指操作数据前没有通过cas设置锁的状态,仅仅通过位运算测试
  • 当前没有线程持有写锁,则简单地返回一个非0的stamp版本,获取stamp后具体操作数据前还需要调用validate方法验证该stamp是否已经不可用(既看当调用tryOptimisticRead返回stamp后看是否有其他线程持有了写锁,如果是返回0,不是则可以使用该stamp进行操作)
  • tryOptimisticRead没有使用CAS设置锁状态,所以不需要显示的释放锁
  • 适用于读多写少的场景
    • 获取读锁只是使用位操作进行检验,不涉及cas操作,所以效率高
    • 同时由于没有真正的锁,保证数据一致性上需要复制一份要操作的变量到方法栈,并且在操作数据时可能其他的线程已经修改了数据,而我们操作的方法栈里的数据只是一个快照,所以最多返回的不是最新的数据

3 Condition

3.1 Condition定义

  • 与wait()和notify()方法的作用大致相同,wait/notify方法是与synchronized关键字联合使用的,Condition是与ReentrantLock联合使用的
  • 在使用Condition.await()方法时,要求线程持有相关重入锁,在Condition.await()方法调用后,这个线程就会释放这把锁
  • 在使用Condition.singal()方法时,也要求线程持有相关重入锁,singal()方法调用后,系统会从当前Condition对象的等待队列中唤醒一个线程,一旦线程被唤醒,将会重新尝试获取重入锁,获取成功可正常执行

3.2 Condition主要方法

  • await() 使当前线程等待,同时释放当前锁,当其他线程中使用signal()方法或者signalAll()方法时,线程会重新获得锁并继续执行.线程被中断时,也能挑出等待.与object.wait()方法类似
  • awaitUninterruptibly()方法与await()基本相同,但不会在等待过程中响应中断
  • singal()方法用于唤醒一个在等待中的线程,singalAll()方法会唤醒所有在等待中的线程,与object.notify()方法类似
ReentrantLock lock =new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
condition.await();

4 ReentrantLock

4.1 ReentrantLock定义

  • ReentrantLock可以完全代替synchronized,JDK5之前重入锁性能远优于synchronized,JDK6开始,两者性能差距不大
  • 如果同一个线程多次获得锁,则在释放锁时也必须释放相同次数,释放的多了会有ILLegalMonitorStateException异常,释放的少了线程还会持有这个锁
  • 特性有 响应中断,等待限时,公平锁
  • 公平锁
    • 不会产生饥饿的现象,new ReentrantLock(boolean fair),fair为true时代表公平锁
    • 公平锁需要维护一个有序队列,因此实现成本比较高,性能低下
    • 默认情况下,可以都使用非公平锁

4.2 ReentrantLock主要方法

  • lock() 获得锁,如果占用则等待
  • lockInterruptibly()获得锁,可以优先响应中断
  • tryLock() 尝试获得锁,如果成功返回true,失败返回false,方法不等待,立即返回
  • lock.tryLock(5,TimeUnit.SECONDS);在给定时间内尝试获取锁
  • unlock()释放锁

4.3 ReentrantLock中AQS过程

  • 假如线程Thread1,Thread2,Thread3同时尝试获取独占锁ReentrantLock,假设Thread1获取到了锁,Thread2/Thread3就会被转换为Node节点并被放入ReentrantLock对应的AQS阻塞队列,而后被阻塞挂起
  • 假设Thread1获取锁后调用了对应的锁创建条件变量1,则Thread1就会释放获取到的锁,然后当前线程就会被转换为Node节点插入条件变量1的条件队列.
  • 由于Thread1释放了锁,所以阻塞到AQS队列里面的Thread2/Thread3就有机会获取到该锁,假如使用的是公平策略, 那么这时候Thread2会获取到该锁,随后从AQS队列里面移除Thread2对应的Node节点

4.4 ReentrantLock实现原理

  • 状态 原子态使用CAS操作来存储当前锁的状态,判断锁是否已经被别的线程持有了
  • 等待队列 所有没有请求到锁的线程,都会进入等待队列进行等待.待有线程释放锁后,系统就能从等待队列中唤醒一个线程,继续工作
  • 阻塞 使用park和unpark来挂起和恢复线程,没有得到锁的线程会被挂起
private final ReentrantLock takeLock = new ReentrantLock(); //take方法使用
private final Condition notEmpty = takeLock.newCondition();
private final ReentrantLock putLock = new ReentrantLock();// put方法使用
private final Condition notFull = putLock.newCondition();
// put方法实现
public void put(E e) throws InterruptedException {
  if (e == null) throw new NullPointerException();
  int c = -1;
  Node<E> node = new Node<E>(e);
  final ReentrantLock putLock = this.putLock;
  final AtomicInteger count = this.count;
  putLock.lockInterruptibly(); // 获取put锁
  try {
    while (count.get() == capacity) { //队列满了
    notFull.await(); // 入队操作需等待
  }
  enqueue(node);	// 插入数据
  c = count.getAndIncrement(); // 更新count,c为原count值
  if (c + 1 < capacity) //如果count<容量
    notFull.signal();	// 未满,可继续入队
  } finally {
    putLock.unlock();
  }
  if (c == 0) //原先队列为空时
  signalNotEmpty(); //通知take方法取数据
}
// take方法实现
public E take() throws InterruptedException {
  E x;
  int c = -1;
  final AtomicInteger count = this.count;
  final ReentrantLock takeLock = this.takeLock;
  takeLock.lockInterruptibly(); // 获取take锁
  try {
    while (count.get() == 0) { //队列空,则等待
    notEmpty.await();	// 等待put方法的通知
  }
  x = dequeue();	// 取得第一个数据
  c = count.getAndDecrement(); //count-1,c为之前count的值
  if (c > 1)	//队列不为空
    notEmpty.signal();	// 通知在notEmpty上等待的线程
  } finally {
    takeLock.unlock(); // 释放锁
  }
  if (c == capacity)// 之前count值==队列容量时
    signalNotFull(); // 通知put方法,已有空间
  return x;
}

5 ReenReadWriteLock

5.1 ReenReadWriteLock定义

  • 是JDK5中提供的读写分离锁,读读=非阻塞,其他=阻塞
  • 如果系统中读的次数远远大于写的次数,读写锁就可以发挥最大功效,提升性能
  • 读写锁的内部维护了一个ReadLock和WriteLock,依赖Sync实现具体功能,Sync继承自AQS,并且提供公平与非公平的实现
  • ReentrantReadWriteLock中state的高16位表示读的次数,低16位表示写的次数
ReentrantReadWirteLock readWriteLock= new ReentrantReadWriteLock();
Lock readLock= readWriteLock.readLock();
Lock writeLock = readWriteLock.writeLock();

5.2 ReenReadWriteLock主要方法

  • 如果其他线程已获取了写锁, 则当前线程获取读锁失败,则进入等待状态
  • 如果当前线程获取了写锁或写锁以未被获取,则当前线程(线程安全,CAS保证),增加读状态,成功获取读锁
  • 读锁的每次释放(线程安全,可能多个线程同时释放读锁)均减少读状态,减少的值为1<<16
  • 所以读锁才能实现读读的过程共享,而读写,写读,写写互斥
void lock()
void lockInterruptibly()
boolean tryLock()
boolean tryLock(long timeout,TimeUnit unit)
void unlock()
protected final int tryAcquireShared(int unused) {
	Thread current = Thread.currentThread();
	int c = getState();
	if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
		return -1; // 如果其他线程已经获取了写锁,则当前线程获取读锁失败,进入等待状态
	int r = sharedCount(c);
	if (!readerShouldBlock() && r < MAX_COUNT &&
		compareAndSetState(c, c + SHARED_UNIT)) {
	if (r == 0) {
		firstReader = current;
		firstReaderHoldCount = 1;
	} else if (firstReader == current) {
		firstReaderHoldCount++;
	} else {
		HoldCounter rh = cachedHoldCounter;
		if (rh == null || rh.tid != getThreadId(current))
			cachedHoldCounter = rh = readHolds.get();
		else if (rh.count == 0)
			readHolds.set(rh);
			rh.count++;
		}
		return 1;
	}
	return fullTryAcquireShared(current);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值