【并发】线程同步——锁Lock与synchronized比较

1、简单使用Lock锁java

    Java 5中引入了新的锁机制——java.util.concurrent.locks中的显式的互斥锁:Lock接口,它提供了比synchronized更加普遍的锁定操做。Lock接口有3个实现它的类:ReentrantLock、ReetrantReadWriteLock.ReadLock和ReetrantReadWriteLock.WriteLock,即重入锁、读锁和写锁。lock必须被显式地建立、锁定和释放,为了可使用更多的功能,通常用ReentrantLock为其实例化。为了保证锁最终必定会被释放(可能会有异常发生),要把互斥区放在try语句块内,并在finally语句块中释放锁,尤为当有return语句时,return语句必须放在try字句中,以确保unlock()不会过早发生,从而将数据暴露给第二个任务。所以,采用lock加锁和释放锁的通常形式以下:算法

Lock lock = new ReentrantLock();//默认使用非公平锁,若是要使用公平锁,须要传入参数true编程

........安全

lock.lock();多线程

try {并发

//更新对象的状态性能

//捕获异常,必要时恢复到原来的不变约束优化

//若是有return语句,放在这里spa

} finally {.net

lock.unlock(); //锁必须在finally块中释放

}

2、ReetrankLock与synchronized比较

一、性能比较

    在JDK1.5中,synchronized是性能低效的。由于这是一个重量级操做,它对性能最大的影响是阻塞的是实现,挂起线程和恢复线程的操做都须要转入内核态中完成,这些操做给系统的并发性带来了很大的压力。相比之下使用Java提供的Lock对象,性能更高一些。Brian Goetz对这两种锁在JDK1.五、单核处理器及双Xeon处理器环境下作了一组吞吐量对比的实验,发现多线程环境下,synchronized的吞吐量降低的很是严重,而ReentrankLock则能基本保持在同一个比较稳定的水平上。但与其说ReetrantLock性能好,倒不如说synchronized还有很是大的优化余地,因而到了JDK1.6,发生了变化,对synchronize加入了不少优化措施,有自适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。致使在JDK1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在将来的版本中还有优化余地,因此仍是提倡在synchronized能实现需求的状况下,优先考虑使用synchronized来进行同步。

下面浅析如下两种锁机制的底层的实现策略。

    互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题,于是这种同步又称为阻塞同步,它属于一种悲观的并发策略,即线程得到的是独占锁。独占锁意味着其余线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引发线程上下文切换,当有不少线程竞争锁的时候,会引发CPU频繁的上下文切换致使效率很低。synchronized采用的即是这种并发策略。

    随着指令集的发展,咱们有了另外一种选择:基于冲突检测的乐观并发策略,通俗地讲就是先进性操做,若是没有其余线程争用共享数据,那操做就成功了,若是共享数据被争用,产生了冲突,那就再进行其余的补偿措施(最多见的补偿措施就是不断地重拾,直到试成功为止),这种乐观的并发策略的许多实现都不须要把线程挂起,所以这种同步被称为非阻塞同步。ReetrantLock采用的即是这种并发策略。

    在乐观的并发策略中,须要操做和冲突检测这两个步骤具有原子性,它靠硬件指令来保证,这里用的是CAS操做(Compare and Swap)。JDK1.5以后,Java程序才可使用CAS操做。咱们能够进一步研究ReentrantLock的源代码,会发现其中比较重要的得到锁的一个方法是compareAndSetState,这里其实就是调用的CPU提供的特殊指令。现代的CPU提供了指令,能够自动更新共享数据,并且可以检测到其余线程的干扰,而compareAndSet() 就用这些代替了锁定。这个算法称做非阻塞算法,意思是一个线程的失败或者挂起不该该影响其余线程的失败或挂起。

    Java 5中引入了注入AutomicInteger、AutomicLong、AutomicReference等特殊的原子性变量类,它们提供的如:compareAndSet()、incrementAndSet()和getAndIncrement()等方法都使用了CAS操做。所以,它们都是由硬件指令来保证的原子方法。

  二、用途比较

    基本语法上,ReentrantLock与synchronized很类似,它们都具有同样的线程重入特性,只是代码写法上有点区别而已,一个表现为API层面的互斥锁(Lock),一个表现为原生语法层面的互斥锁(synchronized)。ReentrantLock相对synchronized而言仍是增长了一些高级功能,主要有如下三项:

    一、等待可中断:当持有锁的线程长期不释放锁时,正在等待的线程能够选择放弃等待,改成处理其余事情,它对处理执行时间很是上的同步块颇有帮助。而在等待由synchronized产生的互斥锁时,会一直阻塞,是不能被中断的。

    二、可实现公平锁:多个线程在等待同一个锁时,必须按照申请锁的时间顺序排队等待,而非公平锁则不保证这点,在锁释放时,任何一个等待锁的线程都有机会得到锁。synchronized中的锁时非公平锁,ReentrantLock默认状况下也是非公平锁,但能够经过构造方法ReentrantLock(ture)来要求使用公平锁。

    三、锁能够绑定多个条件:ReentrantLock对象能够同时绑定多个Condition对象(名曰:条件变量或条件队列),而在synchronized中,锁对象的wait()和notify()或notifyAll()方法能够实现一个隐含条件,但若是要和多于一个的条件关联的时候,就不得不额外地添加一个锁,而ReentrantLock则无需这么作,只须要屡次调用newCondition()方法便可。并且咱们还能够经过绑定Condition对象来判断当前线程通知的是哪些线程(即与Condition对象绑定在一块儿的其余线程)。

三、可中断锁

    ReetrantLock有两种锁:忽略中断锁和响应中断锁。忽略中断锁与synchronized实现的互斥锁同样,不能响应中断,而响应中断锁能够响应中断。

    若是某一线程A正在执行锁中的代码,另外一线程B正在等待获取该锁,可能因为等待时间过长,线程B不想等待了,想先处理其余事情,咱们可让它中断本身或者在别的线程中中断它,若是此时ReetrantLock提供的是忽略中断锁,则它不会去理会该中断,而是让线程B继续等待,而若是此时ReetrantLock提供的是响应中断锁,那么它便会处理中断,让线程B放弃等待,转而去处理其余事情。

  得到响应中断锁的通常形式以下:

ReentrantLock lock = new ReentrantLock();

...........

lock.lockInterruptibly();//获取响应中断锁

try {

//更新对象的状态

//捕获异常,必要时恢复到原来的不变约束

//若是有return语句,放在这里

}finally{

lock.unlock(); //锁必须在finally块中释放

}

3、条件变量实现线程间协做

    在生产者——消费者模型一文中,咱们用synchronized实现互斥,并配合使用Object对象的wait()和notify()或notifyAll()方法来实现线程间协做。Java 5以后,咱们能够用Reentrantlock锁配合Condition对象上的await()和signal()或signalAll()方法来实现线程间协做。在ReentrantLock对象上newCondition()能够获得一个Condition对象,能够经过在Condition上调用await()方法来挂起一个任务(线程),经过在Condition上调用signal()来通知任务,从而唤醒一个任务,或者调用signalAll()来唤醒全部在这个Condition上被其自身挂起的任务。另外,若是使用了公平锁,signalAll()的与Condition关联的全部任务将以FIFO队列的形式获取锁,若是没有使用公平锁,则获取锁的任务是随机的,这样咱们即可以更好地控制处在await状态的任务获取锁的顺序。与notifyAll()相比,signalAll()是更安全的方式。另外,它能够指定唤醒与自身Condition对象绑定在一块儿的任务。Lock和Condition对象只有在更加困难的多线程问题中才是必须的。

读写锁

    另外,synchronized获取的互斥锁不只互斥读写操做、写写操做,还互斥读读操做,而读读操做时不会带来数据竞争的,所以对对读读操做也互斥的话,会下降性能。Java 5中提供了读写锁,它将读锁和写锁分离,使得读读操做不互斥,获取读锁和写锁的通常形式以下:

ReadWriteLock rwl = new ReentrantReadWriteLock();

rwl.writeLock().lock() //获取写锁

rwl.readLock().lock() //获取读锁

   用读锁来锁定读操做,用写锁来锁定写操做,这样写操做和写操做之间会互斥,读操做和写操做之间会互斥,但读操做和读操做就不会互斥。

《Java并发编程实践》一书给出了使用 ReentrantLock的最佳时机:

    当你须要如下高级特性时,才应该使用:可定时的、可轮询的与可中断的锁获取操做,公平队列,或者非块结构的锁。不然,请使用synchronized。   

相关文章
相关标签/搜索