多线程学习-ReentrantLock

前面学习了下synchronized的隐式锁,特点也是说了,不好的地方还望大家多多指教

下面总结下使用ReentrantLock的心得,网上有许多关于synchronized和ReentrantLock用哪个更好的讨论,有兴趣的大家可以搜一下,也根据自己的实际情况区选择

Java官方API 

https://docs.oracle.com/javase/8/docs/api/

ReentrantLock 特点

      1.  可重入互斥锁 

      2. 使用方便,提供公平和非公平两种锁竞争模式(synchroinzed 是非公平竞争模式)

官方建议使用方式

It is recommended practice to always immediately follow a call to lock with a try block, most typically in a before/after construction such as:

 
 class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...
    
   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

看一个简单的例子,先了解下ReentrantLock是运作的

public class ReentrantLockDemo {
    
    protected class MyReentrantLockRunnable implements Runnable{
        final ReentrantLock reentrantLock = new ReentrantLock(true); //采用公平竞争模式
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "就绪");
            reentrantLock.lock();//上锁
            try {
                for(int i=0;i<2;i++){
                    System.out.println(Thread.currentThread().getName() + "执行:"+i);
                }
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                reentrantLock.unlock(); //无论本次执行是否正常都要释放锁,以免造成死锁
            }
            System.out.println(Thread.currentThread().getName() + "完毕");
        }
    }
    
    public static void main(String[] args){
        ReentrantLockDemo reentrantLockDemo = new ReentrantLockDemo();
        MyReentrantLockRunnable runnable = reentrantLockDemo.new MyReentrantLockRunnable();
        Thread t1 = new Thread(runnable,"t1");
        Thread t2 = new Thread(runnable,"t2");
        t1.start();
        t2.start();
    }
}

运行结果: 

t2就绪

t1就绪

t2执行:0

t2执行:1

t2完毕

t1执行:0

t1执行:1

t1完毕  

从运行结果可以看出t2线程首先获得锁,t1进来后在门外面等t2执行完毕释放锁, 方可获得锁继续执行

ReentrantLock锁的等待和唤醒Condition 

看一个典型的生产者和消费者的例子

车间类定义

/**
 * 描述:〈食品工厂〉
 *
 * @author lyh
 * create on 2018/3/7
 * @version 1.0
 */
public class MakeFoodFactory {

    private ReentrantLock lock = new ReentrantLock(true);
    //生产者状态监听
    Condition customerCondition = lock.newCondition();
    //消费者状态监听
    Condition produceCondition = lock.newCondition();
    //工厂的最大容量 如果使用非阻塞队列效率更高
    BlockingQueue<String> blockingQueue = new LinkedBlockingDeque<>(10);

    /**
     * 消费者行为控制
     * @author lyh
     * Created On 2018/3/7 下午5:14
     */
    public void produce(){
        lock.lock();
        try {
            while (blockingQueue.size()>=3) {//当前队列中的面包数大于3的时候,暂停生产面包
                produceCondition.await();
            }
            blockingQueue.put("添加一个面包");
            System.out.println(Thread.currentThread().getName()+"生产一个面包,当前面包数"+blockingQueue.size());
            Thread.sleep(1000);
            customerCondition.signalAll(); //一旦有面包就喊一下消费者区消费   这可以优化
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 消费者行为控制
     * @author lyh
     * Created On 2018/3/7 下午5:14
     */
    public void customer(){
        try{
            lock.lock();
            while(blockingQueue.isEmpty()){  //当队列中没有面包的时候,等待
                customerCondition.await();
            }
            blockingQueue.take();
            System.out.println(Thread.currentThread().getName()+"消费一个面包,当前面包数" +blockingQueue.size());
            Thread.sleep(1000);
            produceCondition.signalAll();  //这同样也可以优化
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}

工人生产控制类

/**
 * 工人生产控制
 * @author lyh
 * create on 2018/3/7
 * @version 1.0
 */
public class MakeFoodRunDemo {

    /**
     *  生产面包
     * Created On 2018/3/7 下午4:04
     */
    protected class FoodCustomer implements Runnable{
        private MakeFoodFactory makeFoodFactory;

        FoodCustomer(MakeFoodFactory makeFoodFactory){
            this.makeFoodFactory = makeFoodFactory;
        }

        @Override
        public void run() {
            for(;;){
                makeFoodFactory.customer();
            }
        }
    }
    /**
     *  消费面包
     * Created On 2018/3/7 下午4:05
     */
    protected class FoodProducer implements Runnable{
        private MakeFoodFactory makeFoodFactory;

        FoodProducer(MakeFoodFactory makeFoodFactory){
            this.makeFoodFactory = makeFoodFactory;
        }

        @Override
        public void run() {
            for(;;){
                makeFoodFactory.produce();
            }
        }
    }

    public static void main(String[] args){
        MakeFoodFactory makeFoodFactory = new MakeFoodFactory();
        MakeFoodRunDemo makeFoodRunDemo = new MakeFoodRunDemo();
        FoodProducer producer = makeFoodRunDemo.new FoodProducer(makeFoodFactory);
        FoodCustomer foodCustomer = makeFoodRunDemo.new FoodCustomer(makeFoodFactory);
        Thread t1 = new Thread(producer,"p1");
        Thread t2 = new Thread(producer,"p2");
        Thread t3 = new Thread(foodCustomer,"c1");
        t1.start();
        t2.start();
        t3.start();
    }

}

运行结果

p2生产一个面包,当前面包数1
p1生产一个面包,当前面包数2
c1消费一个面包,当前面包数1
p2生产一个面包,当前面包数2
p1生产一个面包,当前面包数3
c1消费一个面包,当前面包数2
p2生产一个面包,当前面包数3
c1消费一个面包,当前面包数2
p2生产一个面包,当前面包数3
c1消费一个面包,当前面包数2
p2生产一个面包,当前面包数3
c1消费一个面包,当前面包数2
p2生产一个面包,当前面包数3
c1消费一个面包,当前面包数2
p2生产一个面包,当前面包数3

可以看出每当队列中面包数大于等于3的时候,便开始停工,等待消费者去消费,消费完了再接着生产。其中控制生产还是消费的是ReentantLock的标识生产者状态和消费这状态的Condition,达到条件则进入等待状态。消费这每次消费完之后则唤醒一次生产者,生产者被唤醒后如果满足生产的条件则生产,否则继续等待。

思考:

   1.每次消费都去唤醒一次生产者是不是有必要?

   2.每次生产一个就去唤醒一次消费这是不是有必要

   3.Condition在await()的时候线程有没有释放锁?

对于问题1、2,其实我觉的编程就像生活一样,这里就像是两个工人在一起协作,A1,A2负责做面包,B负责打包面包。显然两个人生产的速度要大于一个人包装的速度(假设生产一个和包装一个所用的时间一致),所以中间会有很多不必要的通知,比如B正在忙着包装的时候就没有必要在去喊,同样消费者也没有必要去喊正在工作的生产者。这中间就需要有一个沟通,如果B没有在打包的时候我们才去喊他,同样需要A1,A2生产的时候我们才去喊人家,这样效率才是最高的。

第三发问题

Causes the current thread to wait until it is signalled or interrupted.

The lock associated with this Condition is atomically released and the current thread becomes disabled for thread scheduling purposes and lies dormant until one of four things happens:

  • Some other thread invokes the signal() method for this Condition and the current thread happens to be chosen as the thread to be awakened; or
  • Some other thread invokes the signalAll() method for this Condition; or
  • Some other thread interrupts the current thread, and interruption of thread suspension is supported; or
  • A "spurious wakeup" occurs.

In all cases, before this method can return the current thread must re-acquire the lock associated with this condition. When the thread returns it is guaranteed to hold this lock.

当这个方法被调用,自动释放锁关联的锁资源,并将当前线程变成disabled状态,

我们添加两个打印语句

 /**
     * 生产行为控制
     * @author lyh
     * Created On 2018/3/7 下午5:14
     */
    public void produce(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"获得锁"+blockingQueue.size());//添加打印
            while (blockingQueue.size()>=3) {//当前队列中的面包数大于3的时候,暂停生产面包
                produceCondition.await();
                System.out.println(Thread.currentThread().getName()+"await"+blockingQueue.size());//添加打印
            }
            blockingQueue.put("添加一个面包");
            System.out.println(Thread.currentThread().getName()+"生产一个面包,当前面包数"+blockingQueue.size());
            Thread.sleep(1000);
            //if(blockingQueue.size()>=3) {
            customerCondition.signalAll(); //一旦有面包就喊一下消费者区消费
            //}
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName()+"释放锁");
            lock.unlock();

        }
    }

执行结果
p1获得锁0
p1生产一个面包,当前面包数1
p1释放锁
p2获得锁1
p2生产一个面包,当前面包数2
p2释放锁
c1消费一个面包,当前面包数1
p1获得锁1
p1生产一个面包,当前面包数2
p1释放锁
p2获得锁2
p2生产一个面包,当前面包数3
p2释放锁
c1消费一个面包,当前面包数2
p1获得锁2
p1生产一个面包,当前面包数3
p1释放锁
p2获得锁3
c1消费一个面包,当前面包数2
p1获得锁2
p1生产一个面包,当前面包数3
p1释放锁
p2await3
c1消费一个面包,当前面包数2
p1获得锁2
p1生产一个面包,当前面包数3
p1释放锁
p2await3
c1消费一个面包,当前面包数2
p1获得锁2
p1生产一个面包,当前面包数3
p1释放锁
p2await3
c1消费一个面包,当前面包数2
p1获得锁2
p1生产一个面包,当前面包数3
p1释放锁
p2await3
c1消费一个面包,当前面包数2
p1获得锁2
p1生产一个面包,当前面包数3
p1释放锁
p2await3

可以看出在在公平竞争此略下,这个p2的状态将会一直是唤醒被disabled 再唤醒在被disable ,因为在p2被唤醒的过程中p1先进入的队列,所以永远都是先执行p1,而p1执行完之后队列的size就变成了3,p2就又要等待。await之后当前线程是被disable了,如果是非公平策略,则两个线程p1和p2都会出现等待的情况

参考:

http://www.cnblogs.com/superfj/p/7543927.html 

JAVA并发编程

JAVA API



猜你喜欢

转载自blog.csdn.net/leeahuamsg/article/details/79473095
今日推荐