Lock的学习与使用

       多线程是复杂的,为了使开发者写出简单、安全的并发程序,JDK提供了大量的API和框架。在上一节,大家已经认识了线程间同步的一个重要手段sychronized,现在,我们来学习一下JDK1.5新增的线程同步工具类Lock。Lock不仅具有sychronized的所有特性,在它的基础上,增加了许多其它的功能,相较于sychronized,Lock更加灵活。当然,我们不是说sychronized完全没有用了,只是Lock的功能更加强大。

       在线程同步中,sychronized关键字是一种最简单的方式,要想我们的程序实现更多更好的功能,Lock是必不可少的。写一个简单的例子,进入到Lock的世界中:

       从代码中看,大家应该发现了三处与业务逻辑不相关的代码,分别是创建lock对象、lock.lock()、lock.unlock()。这三句代码就相当于线程同步控制,也就是所谓的锁操作,其中lock.lock()是加锁,lock.unlock()是解锁。大家或许就会问了,使用Lock线程同步怎么这么麻烦呢?sychronized一个关键字就能做到自动加锁,解锁,lock还需要我们手动操作,强大在哪里呢?朋友们,别急,听我仔细分析一下。使用Lock进行锁操作,我们能够明显的看出哪里需要加锁,哪里需要解锁,一目了然,使用起来非常灵活。另外,这只是一个简单的例子,丰富多彩的情节总是在后面的。

       在上面的代码中,我们使用了try-catch方式,这在实际项目中基本上是一种固定的模式,这是为了防止业务逻辑出现异常,导致资源未释放的问题。 

       Lock锁也是重入锁,表明一个线程能够连续多次获取锁,但是,这里需要特别注意,多次获取,也就相应的需要进行同等数量释放。如果我们在代码中,多释放锁,会得到一个java.lang.IllegalMonitorStateException,相反,少释放锁,那么当前线程依然持有锁,这是很危险的。大家可以按照这两种情况分别写一段代码,看看结果如何。

锁的获取方式:

lock()、lockInterruptibly()、tryLock()或者tryLock(long timeout,TimeUnit unit)。

1.lock(),锁未被使用,直接获取,否则,永久等待。

2.lockInterruptibly(),当前线程未被中断,则获取锁定,如果已被中断则出现异常。处理死锁,很有帮助。

3.tryLock(),限时等待锁。方法中不加时间,如果线程没有获得锁,则立即返回false,否则,返回true。方法中加时间,则表示限时等待,超过时间未获得锁,则线程自动放弃。避免死锁,很有帮助。

测试lockInterruptibly()方法:

       这段代码,很明显,会发生死锁,当线程A获取锁1之后,进行睡眠,线程B获取锁2,之后两个线程互相请求对方持有的锁。

结果:

        从结果看出,两个线程都退出了。使用lockInterruptibly()请求锁,当我们使用interrupte()中断时,线程A放弃了对锁2的请求并释放锁1,然后退出,之后线程B获取到锁1,正常退出。虽然两个线程都退出了,但是真正完成工作的只有线程B。lockInterruptibly()进行锁中断,很轻易就解决了死锁。

测试tryLock()方法:

结果:

       这这里,我们使用tryLock()方式获取锁,结果输出中,两个时间是一致的,这就表明,使用tryLock()会立即去请求锁,当锁被其它线程所持有的时候,会立马返回false。tryLock(long timeout,TimeUnit unit)也是类似,相对来说,只是在获取锁加了等待时间,时间到达还未获取锁,也会立马返回false。利用tryLock()避免死锁是一种好的方式。

公平锁:

        大多数情况下,我们都是使用的非公平锁。通俗来说,锁的获取是随机的,即使线程A先线程B请求锁,线程A也不一定先获取锁,不会按照时间先后顺序,获取资源随机性可能导致饥饿现象,sychronized获取锁就是非公平的。如果我们想要获取公平锁,应该怎样实现呢?下面我们讲一下ReentrantLock的另一构造函数:

       通过源码注释,我们发现,当参数fair为true时,锁时公平的,相反,则是非公平的。公平锁可以帮助我们解决饥饿问题,但是公平锁需要维护一个有序队列,实现成本大,性能相对较低,因此,默认情况下,锁都是非公平的。当我们的业务没有特别需求的情况下,我们尽量使用非公平锁。下面展现了公平锁的特点:

      上述代码,我们使用new ReentrantLock(true)创建对象,指定锁是公平的。run()方法中,我们循环获取锁,看看输出:

      截取一部分输出,大家可以发现,每次获取锁的顺序都是一致的,不会发生一个线程连续把持锁的情况,从而保证了锁的公平性。当我们把fair改为false,输出结果:

       可以看到,一个线程会倾向于在此获取已经持有的锁,这种分配方式比较高效,但是无公平性,很容易出现饥饿现象。

其它方法:

int getHoldCount():获取当前线程持有该锁的次数,也就是调用lock()方法的次数。

int getQueueLength():获取正等待请求锁的线程个数。

boolean isFair():判断当前锁是否是公平锁。

boolean isHeldByCurrentThread():判断当前线程是否持有该锁。

boolean isLocked():获取该锁是否已经被任意线程所持有。

        这一节,我们大概了解Lock的实现类ReentrantLock的常用方法,它的逻辑控制灵活,在多线程程序中,我们完全可以使用ReentrantLock来代替sychronized。下一节我们将继续围绕ReentrantLock重入锁,学习Condition条件和Semaphore信号量。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值