[多线程] 线程同步锁 Lock 的使用 ReentrantLock (生成者和消费者)

版权声明:转载请注明出处 https://blog.csdn.net/weixin_39554102/article/details/85846496

注释来自JDK1.6文档

Lock 常用方法

返回值 方法 注释
void lock() 获取锁。
void unlock() 释放锁
Condition newCondition() 返回绑定到此 Lock 实例的新 Condition 实例。
boolean tryLock() 仅在调用时锁为空闲状态才获取该锁。
boolean tryLock(long time, TimeUnit unit) 如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。
void lockInterruptibly() 如果当前线程未被中断,则获取锁。

Lock 和 ReentrantLock 使用案例

源码下载

CommodityStore

获取商品时, 如果 列表为空, 则进入阻塞状态;
等待商品列表有商品时再获取商品返回,否则一直等待和递归获取

/**
 * 存储商品
 * @author chenyq
 * @email [email protected]
 * @date 2019年1月5日
 */
public class CommodityStore implements Store {

	/**
	 * 存储商品的的列表
	 */
	private List<Commodity> store = new ArrayList<>();
	
	/**
	 * 锁
	 */
	private final Lock lock = new ReentrantLock();
	private Condition condition = lock.newCondition();
	
	/**
	 * 放入商品时, 会锁顶这个列表, 添加完成后会唤醒等待的线程
	 */
	@Override
	public void put(Commodity commodity) {
		lock.lock();
		try {
			store.add(commodity);
			condition.signal();
		} finally {
			lock.unlock();
		}
	}
	
	/**
	 * 获取商品, 会移除列表中的这个元素, 假若列表没有这个元素, 则等待
	 */
	@Override
	public Commodity get() {
		lock.lock();
		try {
			if (store.isEmpty()) {
				condition.await();
			}
			return store.remove(0);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
		return get();
	}
}

Store

/**
 * 商店
 * @author chenyq
 * @email [email protected]
 * @date 2019年1月5日
 */
public interface Store {

	void put(Commodity commodity);
	
	Commodity get();
}

Commodity

/**
 * 商品
 * 
 */
public class Commodity {
	
	private Integer id;
	private String name;
	
	public Commodity(Integer id, String name) {
		super();
		this.id = id;
		this.name = name;
	}

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "[" + id + ", " + name + "]";
	}
	
	
}

Producer

/**
 * 生产者
 * 
 * @author chenyq
 * @email [email protected]
 * @date 2019年1月5日
 */
public class Producer implements Runnable {

	private Store store;
	
	private int count;
	
	public Producer(Store store) {
		super();
		this.store = store;
	}

	@Override
	public void run() {
		while(true) {
			sleep(500);
			int i = count();
			Commodity c = new Commodity(i, "name-" + i);
			System.out.print(Thread.currentThread().getName() + " : " + c + "  =>>>>  ");
			store.put(c);
		}
	}

	static void sleep(long m) {
		try {
			Thread.sleep(m);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	int count() {
		return count++;
	}
}
/**
 * 消费者线程
 * @author chenyq
 * @email [email protected]
 * @date 2019年1月5日
 */
public class Consumer implements Runnable {

	private Store store;
	
	public Consumer(Store store) {
		super();
		this.store = store;
	}

	@Override
	public void run() {
		while(true)
			System.out.println(Thread.currentThread().getName() + " : " + store.get());
	}

}

LockTest

public class LockTest {

	public static void main(String[] args) {
		Store store = new CommodityStore();
		new Thread(new Producer(store), "producer-1").start();
		new Thread(new Consumer(store), "consumer-1").start();
	}

}

源码在jdk1.8下运行测试: 结果

producer-1 : [0, name-0]  =>>>>  consumer-1 : [0, name-0]
producer-1 : [1, name-1]  =>>>>  consumer-1 : [1, name-1]
producer-1 : [2, name-2]  =>>>>  consumer-1 : [2, name-2]
producer-1 : [3, name-3]  =>>>>  consumer-1 : [3, name-3]
producer-1 : [4, name-4]  =>>>>  consumer-1 : [4, name-4]
producer-1 : [5, name-5]  =>>>>  consumer-1 : [5, name-5]
producer-1 : [6, name-6]  =>>>>  consumer-1 : [6, name-6]

Lock 接口简介:

public interface LockLock

实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。

锁是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问。一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁。不过,某些锁可能允许对共享资源并发访问,如 ReadWriteLock 的读取锁。

synchronized 方法或语句的使用提供了对与每个对象相关的隐式监视器锁的访问,但却强制所有锁获取和释放均要出现在一个块结构中:当获取了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的词法范围内释放所有锁。

虽然 synchronized 方法和语句的范围机制使得使用监视器锁编程方便了很多,而且还帮助避免了很多涉及到锁的常见编程错误,但有时也需要以更为灵活的方式使用锁。例如,某些遍历并发访问的数据结果的算法要求使用 “hand-over-hand” 或 “chain locking”:获取节点 A 的锁,然后再获取节点 B 的锁,然后释放 A 并获取 C,然后释放 B 并获取 D,依此类推。Lock 接口的实现允许锁在不同的作用范围内获取和释放,并允许以任何顺序获取和释放多个锁,从而支持使用这种技术。

随着灵活性的增加,也带来了更多的责任。不使用块结构锁就失去了使用 synchronized 方法和语句时会出现的锁自动释放功能。在大多数情况下,应该使用以下语句:

 Lock l = ...; 
 l.lock();
 try {
     // access the resource protected by this lock
 } finally {
     l.unlock();
 }

锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁。
Lock 实现提供了使用 synchronized 方法和语句所没有的其他功能,包括提供了一个非块结构的获取锁尝试 (tryLock())、一个获取可中断锁的尝试 (lockInterruptibly()) 和一个获取超时失效锁的尝试 (tryLock(long, TimeUnit))。

Lock 类还可以提供与隐式监视器锁完全不同的行为和语义,如保证排序、非重入用法或死锁检测。如果某个实现提供了这样特殊的语义,则该实现必须对这些语义加以记录。

注意,Lock 实例只是普通的对象,其本身可以在 synchronized 语句中作为目标使用。获取 Lock 实例的监视器锁与调用该实例的任何 lock() 方法没有特别的关系。为了避免混淆,建议除了在其自身的实现中之外,决不要以这种方式使用 Lock 实例。

除非另有说明,否则为任何参数传递 null 值都将导致抛出 NullPointerException。

内存同步
所有 Lock 实现都必须 实施与内置监视器锁提供的相同内存同步语义,如 The Java Language Specification, Third Edition (17.4 Memory Model) 中所描述的:

成功的 lock 操作与成功的 Lock 操作具有同样的内存同步效应。
成功的 unlock 操作与成功的 Unlock 操作具有同样的内存同步效应。
不成功的锁定与取消锁定操作以及重入锁定/取消锁定操作都不需要任何内存同步效果。
实现注意事项
三种形式的锁获取(可中断、不可中断和定时)在其性能特征、排序保证或其他实现质量上可能会有所不同。而且,对于给定的 Lock 类,可能没有中断正在进行的 锁获取的能力。因此,并不要求实现为所有三种形式的锁获取定义相同的保证或语义,也不要求其支持中断正在进行的锁获取。实现必需清楚地对每个锁定方法所提供的语义和保证进行记录。还必须遵守此接口中定义的中断语义,以便为锁获取中断提供支持:完全支持中断,或仅在进入方法时支持中断。

由于中断通常意味着取消,而通常又很少进行中断检查,因此,相对于普通方法返回而言,实现可能更喜欢响应某个中断。即使出现在另一个操作后的中断可能会释放线程锁时也是如此。实现应记录此行为。

ReentrantLock 简介:

public class ReentrantLockextends Objectimplements Lock, Serializable

一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

ReentrantLock 将由最近成功获得锁,并且还没有释放该锁的线程所拥有。当锁没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁并返回。如果当前线程已经拥有该锁,此方法将立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查此情况是否发生。

此类的构造方法接受一个可选的公平 参数。当设置为 true 时,在多个线程的争用下,这些锁倾向于将访问权授予等待时间最长的线程。否则此锁将无法保证任何特定访问顺序。与采用默认设置(使用不公平锁)相比,使用公平锁的程序在许多线程访问时表现为很低的总体吞吐量(即速度很慢,常常极其慢),但是在获得锁和保证锁分配的均衡性时差异较小。不过要注意的是,公平锁不能保证线程调度的公平性。因此,使用公平锁的众多线程中的一员可能获得多倍的成功机会,这种情况发生在其他活动线程没有被处理并且目前并未持有锁时。还要注意的是,未定时的 tryLock 方法并没有使用公平设置。因为即使其他线程正在等待,只要该锁是可用的,此方法就可以获得成功。

建议总是 立即实践,使用 lock 块来调用 try,在之前/之后的构造中,最典型的代码如下:

 class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() { 
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

除了实现 Lock 接口,此类还定义了 isLocked 和 getLockQueueLength 方法,以及一些相关的 protected 访问方法,这些方法对检测和监视可能很有用。

该类的序列化与内置锁的行为方式相同:一个反序列化的锁处于解除锁定状态,不管它被序列化时的状态是怎样的。

此锁最多支持同一个线程发起的 2147483648 个递归锁。试图超过此限制会导致由锁方法抛出的 Error。

猜你喜欢

转载自blog.csdn.net/weixin_39554102/article/details/85846496
今日推荐