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

注释来自JDK1.6文档

Lock 常用方法

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

Lock 和 ReentrantLock 使用案例

源码下载

CommodityStore

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

/**
 * 存储商品
 * @author chenyq
 * @email 806512500@qq.com
 * @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 806512500@qq.com
 * @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 806512500@qq.com
 * @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 806512500@qq.com
 * @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。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值