Java多线程Day18-JUC锁之独占锁ReentrantLock

108 阅读11分钟

独占锁ReentrantLock

基本概念

  • ReentrantLock是一个可重入的互斥锁,所以称作 "独占锁"

    • 可重入: ReentrantLock可以被单个线程多次获取
    • 独占锁: ReentrantLock在同一个时间点只能被一个线程锁持有
  • 锁是为了保护竞争资源,防止多个线程同时操作资源而出错

  • ReentrantLock在同一个时间点只能被一个线程获取,当某个线程获取到锁时,其余线程必须等待

  • ReentrantLock分为公平锁非公平锁, 区别体现在获取锁的机制上是否公平 . ReentrantLock是通过一个FIFO等待队列来管理获取锁的所有线程的

    • 在公平锁机制下: 线程必须依次排队获取锁
    • 在非公平锁机制下: 在锁是可获取的状态时,无论线程是否在队列的开头都会获取锁

ReentrantLock函数列表

ReentrantLock

/**
 * 创建一个ReentrantLock, 默认是非公平锁
 */
public ReentrantLock() {
	sync = new NonfairSync();
}

/**
 * 创建一个fair策略的ReentrantLock
 * 	- fair为true表示公平锁
 * 	- fair为false表示非公平锁
 *  
 * @param fair 公平策略fair
 */
public ReentrantLock(boolean fair) {
	sync = fair ? new FairSync() : new NonfairSync();
}

getHoldCount

/**
 * 获取当前线程持有锁的次数
 *  
 * @return int 当前线程持有锁的次数
 */
public int getHoldCount() {
	return sync.getHoldCount();
}

getOwner

/**
 * 获取当前持有该锁的线程,如果该锁未被任何线程持有则返回null
 *   
 * @return Thread 当前持有该锁的线程
 */
protected Thread getOwner() {
	return sync.getOwner();
}

getQueuedThreads

/**
 * 获取一个包含可能正在等待该锁的线程的集合Collection
 *   
 * @return Collection<Thread> 一个包含可能正在等待该锁的线程的集合
 */
protected Collection<Thread> getQueuedThreads() {
	return sync.getQueuedThreads();
}

getQueueLength

/**
 * 获取等待该锁的线程的可能的个数
 *   
 * @return int 等待该锁的线程的可能个数
 */
public final int getQueueLength() {
	return sync.getQueueLength();
}

getWaitingThreads

/**
 * 获取一个可能正在等待该锁相关联的条件的线程的集合Collection
 *  
 * @param condition 该锁相关联的条件
 * @return Collection<Thread> 可能正在等待该锁相关联的条件的线程的集合Collection
 */
protected Collection<Thread> getWaitingThreads(Condition condition) {
	if (condition == null)
		throw new NullPointerException();
	if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
		throw new IllegalArgumentException("not owner");
	return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition);
}

getWaitQueueLength

/**
 * 获取一个可能正在等待该锁相关联的条件的线程的个数
 *  
 * @param condition 该锁相关联的条件
 * @return Collection<Thread> 可能正在等待该锁相关联的条件的线程的个数
 */
public int getWaitQueueLength(Condition condition) {
	if (condition == null)
		throw new NullPointerException();
	if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
		throw new IllegalArgumentException("not owner");
	return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition);
}

hasQueuedThread

/**
 * 判断给定的线程是否正在等待获取该锁
 *  
 * @param thread 给定的线程
 * @return boolean 给定的线程是否正在等待获取该锁
 */
public final boolean hasQueuedThread(Thread thread) {
	return sync.isQueued(thread);
}

hasQueuedThreads

/**
 * 判断是否存在正在等待获取该锁的线程
 *  
 * @return boolean 是否存在正在等待获取该锁的线程
 */
public final boolean hasQueuedThreads() {
	return sync.hasQueuedThreads();
}

hasWaiters

/**
 * 判断是否存在正在等待该锁相关联的条件的线程
 *  
 * @param condition 该锁相关联的条件
 * @return boolean 是否存在正在等待该锁相关联条件的线程
 */
public boolean hasWaiters(Condition condition) {
	if (condition == null)
		throw new NullPointerException();
	if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
		throw new IllegalArgumentException("not owner");
	return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
}

isFair

/**
 * 判断该锁是否是公平锁
 * 	- 如果是公平锁则返回true
 *  - 如果是非公平锁则返回false 
 *  
 * @return boolean 该锁是否是公平锁
 */
public final boolean isFair() {
	return sync instanceof FairSync;
}

isHeldByCurrentThread

/**
 * 判断该锁是否被当前线程所持有
 *  
 * @return boolean 该锁是否被当前线程所持有
 */
public boolean isHeldByCurrentThread() {
	return sync.isHeldExclusively();
}

isLocked

/**
 * 判断该锁是否被线程所持有
 *  
 * @return boolean 该锁是否被线程所持有
 */
public boolean isLocked() {
	return sync.isLocked();
}

lock

/**
 * 获取一个锁
 */
public void lock() {
	sync.lock();
}

lockInterruptibly

/** 
 * 获取一个锁,除非该当前线程被中断
 * 即在当前线程未被中断的情况下获取一个锁
 */
public void lockInterruptibly() throws InterruptedException {
	sync.acquireInterruptibly(1);
}

newCondition

/** 
 * 创建一个Lock锁实例的条件Condition实例
 *  
 * @return Condition 与Lock实例相关的Condition实例
 */
public Condition newCondition() {
	return sync.newCondition();
}

tryLock

/** 
 * 在调用时,如果该锁未被其余线程持有的时候,获取该锁
 *  
 * @return boolean 是否成功获取到该锁
 */
public boolean tryLock() {
	return sync.nonfairTryAcquire(1);
}

/** 
 * 如果当前线程在给定的时间内未被其余线程持有,并且当前线程未被中断,获取该锁
 *  
 * @param timeout 给定的过期时间
 * @param unit 给定的时间单位
 * @return boolean 是否成功获取到该锁
 */
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
	return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

unlock

/** 
 * 试图释放该锁
 * 	- 如果当前的线程时该锁的持有者,那么持有的次数将会减少
 * 	- 如果持有的锁次数为0,那么就会释放该锁
 * 	
 */
public void unlock() {
	sync.release(1);
}

ReentrantLock示例

lock

  • Depot:
class Depot {
	/**
	 * 仓库的实际数量
	 */
	private int size;
	
	/**
	 * 独占锁 
	 */
	private Lock lock;

	public Depot() {
		this.size = 0;
		// 通过ReentrantLock实现独占锁
		this.lock = new ReentrantLock();
	}

	/**
	 * 生产 
	 *  
	 * @param val 生产的数量
	 */
	public void produce(int val) {
		lock.lock();
		try {
			size += val;
			System.out.printf("线程-%s生产%d个产品,仓库的实际数量为%d\n", Thread.currentThread().getName(), val, size);
		} finally {
			lock.unlock();
		}
	}

	/**
	 * 消费 
	 *  
	 * @param val 消费的数量
	 */
	public void consume(int val) {
		lock.lock();
		try {
			size -= val;
			System.out.printf("线程-%s消费%d个产品,仓库的实际数量为%d\n", Thread.currentThread().getName(), val, size);
		} finally {
			lock.unlock();
		}
	}
}
  • Producer:
class Producer {
	
	private Depot depot;

	public Producer() {
		this.depot = depot;
	}

	public void produce(final int val) {
		new Thread() {
			public void run() {
				depot.produce(val);
			}
		}.start();
	}
}
  • Consumer:
class Consumer {
	
	private Depot depot;

	public Consumer() {
		this.depot = depot;
	}

	public void consume() {
		new Thread() {
			public void run(final int val) {
				depot.consume(val);
			}
		}.start();
	}
}
  • LockTest:
public Class LockTest {
	public void main(String[] args) {
		Depot depot = new Depot();
		Producer producer = new Producer(depot);
		Consumer consumer = new Consumer(depot);

		producer.produce(60);
		producer.produce(120);
		consumer.consume(90);
		consumer.consume(150);
		producer.produce(110);
	}
}
线程-Thread-0生产60个产品,仓库的实际数量为60

线程-Thread-1生产120个产品,仓库的实际数量为180

线程-Thread-2消费90个产品,仓库的实际数量为90

线程-Thread-3消费150个产品,仓库的实际数量为-60

线程-Thread-4生产110个产品,仓库的实际数量为50
  • 仓库Depot:

    • 通过produce() 生产仓库中的货物

    • 通过consume() 消费仓库中的货物

    • 通过独占锁lock实现对仓库的互斥访问:

      • 在进行生产或者消费操作仓库中的货品前,首先通过lock锁住仓库
      • 在进行生产或者消费操作仓库中的货品后,再通过unlock解锁
  • 生产者Producer:

    • 通过调用Producer中的produce() 方法可以新建一个线程生产仓库中的货物
  • 消费者Consumer:

    • 通过调用Consumer中的consume() 方法可以新建一个线程消费仓库中的货物
  • 客户端LockTest:

    • 在客户端主线程main中,新建一个生产者producer和一个消费者consumer, 用于生产和消费仓库中的货物
  • 该示例存在的问题:

    • 现实中,仓库的货物数量不可能为负数,但是该示例模型中的仓库数量可以为负数,这与现实中的实际情况相矛盾
    • 现实中,仓库的容量是有限制的,但是该示例模型中的仓库数量是没有限制的

unlock

  • Depot: 去掉对仓库的生产和消费方法的锁
class Depot {
	/**
	 * 仓库的实际数量
	 */
	private int size;
	
	/**
	 * 独占锁 
	 */
	private Lock lock;

	public Depot() {
		this.size = 0;
		// 通过ReentrantLock实现独占锁
		this.lock = new ReentrantLock();
	}

	/**
	 * 生产 
	 *  
	 * @param val 生产的数量
	 */
	public void produce(int val) {
		size += val;
		System.out.printf("线程-%s生产%d个产品,仓库的实际数量为%d\n", Thread.currentThread().getName(), val, size);
	}

	/**
	 * 消费 
	 *  
	 * @param val 消费的数量
	 */
	public void consume(int val) {
		size -= val;
		System.out.printf("线程-%s消费%d个产品,仓库的实际数量为%d\n", Thread.currentThread().getName(), val, size);
	}
}
  • Producer:
class Producer {
	
	private Depot depot;

	public Producer() {
		this.depot = depot;
	}

	public void produce(final int val) {
		new Thread() {
			public void run() {
				depot.produce(val);
			}
		}.start();
	}
}
  • Consumer:
class Consumer {
	
	private Depot depot;

	public Consumer() {
		this.depot = depot;
	}

	public void consume() {
		new Thread() {
			public void run(final int val) {
				depot.consume(val);
			}
		}.start();
	}
}
  • LockTest:
public Class LockTest {
	public void main(String[] args) {
		Depot depot = new Depot();
		Producer producer = new Producer(depot);
		Consumer consumer = new Consumer(depot);

		producer.produce(60);
		producer.produce(120);
		consumer.consume(90);
		consumer.consume(150);
		producer.produce(110);
	}
}
线程-Thread-0生产60个产品,仓库的实际数量为-60

线程-Thread-1生产120个产品,仓库的实际数量为-60

线程-Thread-2消费90个产品,仓库的实际数量为-60

线程-Thread-3消费150个产品,仓库的实际数量为-60

线程-Thread-4生产110个产品,仓库的实际数量为50
  • 该示例去掉了仓库的生产和消费方法的锁,没有实现对仓库的互斥访问.导致出现意想不到的数据

Condition

  • Condition: 通过Condition增加锁的条件,解决以下两个问题

    • 仓库的容量为负数
    • 仓库的容量没有限制
  • Condition需要和Lock联合使用:

    • 通过Conditionawait() 方法,使得线程阻塞,类似于线程的wait()方法
    • 通过Conditionsignal() 方法,使得线程唤醒,类是于线程的notify()方法
  • Depot:

class Depot {
	
	/**
	 * 仓库的容量
	 */
	private int capacity;
	
	/**
	 * 仓库的数量
	 */
	private int size;
	
	/**
	 * 独占锁
	 */
	private Lock lock;

	/**
	 * 生产条件
	 */
	private Condition fullCondition;

	/**
	 * 消费条件
	 */
	private Condition emptyCondition;

	public Depot(int capacity) {
		this.capacity = capacity;
		this.size = 0;
		this.lock = new ReetrantLock();
		this.fullCondition = lock.newCondition();
		this.emptyCondition = lock.newCondition();
	}

	/**
	 * 生产
	 *  
	 * @param val 生产的数量
	 */
	public void produce(int val) {
		lock.lock();
		try {
			// 想要生产的数量. 有可能想要生产的数量太多,需要多次生产
			int want = val;
			while (want > 0) {
				// 当库存满时,等待消费者消费产品
				while (size >= capacity) {
					fullCondition.await();
					/*
					 * 获取实际生产的数量,即库存中新增的数量:
					 * 	- 如果 库存 + 想要生产的数量 > 总的容量, 则 实际生产数量 = 总的容量 - 库存
					 * 	- 如果 库存 + 想要生产的数量 <= 总的容量, 则 实际生产的数量 = 想要生产的数量 
					 */
					 int increment = (size + want) > capacity ? (capacity - size) : want;
					 size += increment;
					 want -= increment;
					 System.out.printf("线程-%s生产%d个产品,剩余想要生产数量为%d,实际生产数量%d,仓库的实际数量为%d\n", Thread.currentThread().getName(), want, increment, val, size);
					 // 通知消费者进行消费
					 emptyCondition.signal();
				}
			} 
		} catch (InterruptedException e) {
				e.printStackTrace();
		} finally {
				lock.unlock();
		}
	}

	/**
	 * 消费
	 *  
	 * @param val 消费的数量
	 */
	public void consume(int val) {
		lock.lock();
		try {
			// want表示客户端想要消费的产品
			int want = val;
			while (want > 0) {
				// 当仓库中的库存为0时,等待消费者生产产品
				while (size <= 0) {
					emptyCondition.await();
					/*
					 * 获取实际消费的数量,即库存中减少的数量:
					 * 	- 如果 库存 < 想要消费的数量, 则 实际消费数量 = 库存
					 * 	- 如果 库存 >= 想要生产的数量, 则 实际消费的数量 = 想要消费的数量 
					 */
					 int decrement = size < want ? size : want;
					 size -= decrement;
					 want -= decrement
					 System.out.printf("线程-%s消费%d个产品,剩余想要消费数量为%d,实际消费数量为%d,仓库的实际数量为%d\n", Thread.currentThread().getName(), val, size);
					 // 通知生产者进行生产
					 fullCondition.signal();
				}
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
}
  • Producer:
class Producer {
	private Depot depot;

	public producer(Depot depot) {
		this.depot = depot;
	}

	/**
	 * 生产产品
	 *  
	 * @param val 生产的产品数量
	 */
	public void produce(final int val) {
		new Thread() {
			public void run() {
				depot.produce(val);
			}
		}.start();
	}
}
  • Customer:
class Customer {
	private Depot depot;

	public Customer(Depot depot) {
		this.depot = depot;
	}
	
	/**
	 * 消费产品
	 *  
	 * @param val 消费产品的数量
	 */
	public void consume(final int val) {
		new Thread() {
			public void run() {
				depot.consume(val);
			}
		}.start();
	}
}
  • ConditionTest:
public class ConditionTest {
	public static void main(String[] args) {
		Depot depot = new Depot(100);
		Producer producer = new Producer(depot);
		Consumer consumer = new Consumer(depot);

		producer.produce(60);
		producer.produce(120);
		consumer.consume(90);
		consumer.consume(150);
		producer.produce(110);
	}
}
线程-Thread-0生产60个产品,剩余想要生产数量为0,实际生产的数量为60,仓库的实际数量为60

线程-Thread-1生产120个产品,剩余想要生产数量为80,实际生产的数量为40,仓库的实际数量为100

线程-Thread-2消费90个产品,剩余想要消费数量为0,实际消费的数量为90,仓库的实际数量为10

线程-Thread-3消费150个产品,剩余想要消费数量为140,实际消费的数量为10,仓库的实际数量为0

线程-Thread-4生产110个产品,剩余想要生产数量为10,实际生产的数量为100,仓库的实际数量为100

线程-Thread-3消费150个产品,剩余想要消费数量为40,实际消费的数量为100,仓库的实际数量为0

线程-Thread-4生产110个产品,剩余想要生产数量为0,实际生产的数量为10,仓库的实际数量为10

线程-Thread-3消费150个产品,剩余想要消费数量为30,实际生产的数量为10,仓库的实际数量为0

线程-Thread-1生产120个产品,剩余想要生产数量为0,实际生产的数量为80,仓库的实际数量为80

线程-Thread-3消费150个产品,剩余想要消费数量为0,实际消费的数量为30,仓库的实际数量为50