Java并发编程(三)可重入锁 ReentrantLock 及其他显式锁相关问题

1、跟 Synchronized 相比,可重入锁 ReentrantLock 其实现原理有什么不同

(1)底层实现
synchronized是JVM层面的锁,是java关键字。而ReentrantLock是API层面的互斥锁。

(2)是否可以手动释放
synchronized不需要手动释放,代码执行完之后系统自动让线程释放对锁的占用;
ReentrantLock则需要用户去手动释放锁,如果没有手动释放锁,可能会导致死锁。一般通过lock()和unlock()方法配合try/finally语句来实现。

 public class testReentrantLock{
    private Lock lock = new ReentrantLock();
	public void increment() throws Exception {
	        lock.lock();
	        try {
	           ...........
	        } catch (Exception e) {
	            e.printStackTrace();
	        } finally {
	            lock.unlock();
	        }
	    }
}

(3)是否可中断
synchronized是不可中断的类型,除非加锁的代码中出现异常或正常执行完成。
ReentrantLock是可以中断的,可以通过 tryLock(long timeout, TimeUnit unit)方法设置超时,或者调用lockInterruptibly()方法进行中断。
源码:
tryLock(long timeout, TimeUnit unit)

    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

lockInterruptibly()

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

(4)是否公平锁
Synchronized是非公平锁。
ReentrantLock默认是非公平锁,但是也可以通过参数设置为公平锁。通过构造方法传入boolean类型参数,false默认是非公平锁,true为公平锁。

源码:

// 默认是非公平锁
public ReentrantLock() {
        sync = new NonfairSync();
}

// 根据传入Boolean类型决定公平锁还是非公平锁
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

(5)锁是否可以绑定Condition
synchronized不能绑定
ReentrantLock可以绑定,需要结合await() 、signal()、signalAll()等方法进行操作。
await方法类似Object类的wait()方法,阻塞一个线程;signal()、signalAll()类似Object类的notify()/notifyAll()唤醒一个线程。

每一个Lock可以有任意数据的Condition对象,Condition是与Lock绑定的。Condition的强大之处在于它可以为多个线程间建立不同的Condition。

比如,使用Condition实现生产者-消费者模型:
其中一个是生产者,用于将消息放入缓冲区;消费者从缓冲区取数据。

现在有一个问题,当缓冲区满了的时候,而此时生产者还要往缓冲区放数据的时候,此时解决办法就是让生产者休眠,等消费者从缓冲区取出一个数据之后,再唤醒生产者。
同样的,当缓冲区已经空了,而消费者还要来取数据时,此时要让消费者进行休眠,等待生产者放入一个数据之后再唤醒消费者。

class BoundedBuffer {
   final Lock lock = new ReentrantLock();//锁对象
   final Condition notFull  = lock.newCondition();//写线程条件 
   final Condition notEmpty = lock.newCondition();//读线程条件 

   final Object[] items = new Object[100];//缓存队列
   int putptr/*写索引*/, takeptr/*读索引*/, count/*队列中存在的数据个数*/;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length)//如果队列满了 
         notFull.await();//阻塞写线程
       items[putptr] = x;//赋值 
       if (++putptr == items.length) putptr = 0;//如果写索引写到队列的最后一个位置了,那么置为0
       ++count;//个数++
       notEmpty.signal();//唤醒读线程
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0)//如果队列为空
         notEmpty.await();//阻塞读线程
       Object x = items[takeptr];//取值 
       if (++takeptr == items.length) takeptr = 0;//如果读索引读到队列的最后一个位置了,那么置为0
       --count;//个数--
       notFull.signal();//唤醒写线程
       return x;
     } finally {
       lock.unlock();
     }
   } 
 }

(6)锁的对象
synchronized锁的是对象,锁是保存在对象头里面的,根据对象头标识是否有线程获得锁/争抢锁。
ReentrantLock锁的是线程,根据进入的线程和int类型的state标识锁的获得/争抢。

2、ReentrantLock 是如何实现可重入性的?

ReentrantLock 内部自定义了 同步器Sync,其实就是加锁的时候通过CAS算法,将线程对象放到一个双向链表中,每次获取锁的时候,看下当前维护的那个线程ID和当前请求的线程ID是否一样,就可以实现重入了。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值