Lock锁介绍:
在java中可以使用 synchronized 来实现多线程下对象的同步访问,为了获得更加灵活使用场景、高效的性能,java还提供了Lock接口及其实现类ReentrantLock和读写锁 ReentrantReadWriteLock。
相比synchronized来实现同步,使用Lock实现同步主要有以下差异性:
1、使用synchronized关键字时,锁的控制和释放是在synchronized同步代码块的开始和结束位置。而在使用Lock实现同步时,锁的获取和释放可以在不同的代码块、不同的方法中。这一点是基于使用者手动获取和释放锁的特性。
2、Lock接口提供了试图获取锁的tryLock()方法,在调用tryLock()获取锁失败时返回false,这样线程可以执行其它的操作 而不至于使线程进入休眠。tryLock()方法可传入一个long型的时间参数,允许在一定的时间内来获取锁。
3、Lock接口的实现类ReentrantReadWriteLock提供了读锁和写锁,允许多个线程获得读锁、而只能有一个线程获得写锁。读锁和写锁不能同时获得。实现了读和写的分离,这一点在需要并发读的应用中非常重要,如lucene允许多个线程读取索引数据进行查询但只能有一个线程负责索引数据的构建。
4、基于以上3点,lock来实现同步具备更好的性能。
Lock锁与条件同步:
与synchronized类似,Lock锁也可以实现条件同步。在java的concurrent包中提供了 Condition 接口及其实现类ConditionObject。
当满足一定条件时,调用Condition的await()方法使当前线程进入休眠状态进行等待。调用Condition的signalAll()方法唤醒因await()进入休眠的线程。
在博文中,我们使用synchronized实现了一个生产者-消费者模型,在这里,来试试使用Lock锁及其同步条件来实现同样的一个生产者-消费者模型:
import java.util.ArrayList;
import java.util.List;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;class Goods{
private String goodsName; private Integer count=0; private Integer full; //获得lock对象 private Lock lock=new ReentrantLock(); //获得生产者等待队列对象 private Condition productorCondition=lock.newCondition(); //获得消费者等待队列对象 private Condition customerCondition=lock.newCondition(); public Goods(Integer full) { this.full = full; } public void setGoods(String goodsName){ try{//使用锁实现同步,获取所得操作,当锁被其他线程占用时,当前线程将进入休眠
lock.lock(); while (this.count==this.full){ System.out.println("商品库存很多,快来购买哦"); try {//满足条件时,线程休眠并释放锁。当调用 signalAll()时。线程唤醒并重新获得锁
productorCondition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } this.goodsName=goodsName; this.count++; System.out.println(Thread.currentThread().getName()+"生产"+this.toString());//唤醒因productorCondition.await()休眠的线程
customerCondition.signalAll(); }finally { lock.unlock();//解锁 } } public void getGoods(){ try { lock.lock(); while (this.count==0){ System.out.println("商品正在生产中,亲稍等哦"); try { customerCondition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } this.count--; System.out.println(Thread.currentThread().getName()+"消费"+this.toString());//这个时候已经生产完了,生产者唤醒消费者进行消费
productorCondition.signalAll(); }finally { lock.unlock(); } }@Override
public String toString() { return "Goods{" + "goodsName='" + goodsName + '\'' + ", count=" + count + '}'; }} class Productor implements Runnable{ private Goods goods;public Productor(Goods goods) {
this.goods = goods; }@Override
public void run() { while (true){ this.goods.setGoods("mac"); } }} class Customer implements Runnable{ private Goods goods;public Customer(Goods goods) {
this.goods = goods; }@Override
public void run() { while (true){ this.goods.getGoods(); } }} public class Test{ public static void main(String[] args) { Goods goods=new Goods(20); Productor productor=new Productor(goods); Customer customer=new Customer(goods); List<Thread> list=new ArrayList<>(); for(int i=0;i<5;i++){ Thread thread=new Thread(productor,"生产者"+i); list.add(thread); } for(int i=0;i<10;i++){ Thread thread=new Thread(customer,"消费者"+i); list.add(thread); } for(Thread thread:list){ thread.start(); } }}//控制台打印结果部分截图
总结:
不管是synchronized关键字还是Lock锁,都是用来在多线程的环境下对资源的同步访问进行控制,用以避免因多个线程对数据的并发读写造成的数据混乱问题。与synchronized不同的是,Lock锁实现同步时需要使用者手动控制锁的获取和释放,其灵活性使得可以实现更复杂的多线程同步和更高的性能,但同时,使用者一定要在获取锁后及时捕获代码运行过程中的异常并在finally代码块中释放锁。