之前总结了部分无锁机制的多线程基础,理想的状态当然是利用无锁同步解决多线程程序设计的问题。但是实际碰到的问题使得很多情况下,我们不得不借助锁同步来保证线程安全。自从JDK5开始,有两种机制来屏蔽代码块在并行访问的干扰,synchronized关键字已经介绍过了部分内容,所以这次简单的说说另一种锁机制:ReentrantLock。
对于synchronized的缺点之前也简单的说了一些,实际使用中比较烦扰的几点是:a.只有一个"条件"与锁相关联,这对于大量并发线程的情况是很难管理(等待和唤醒);b.多线程竞争一个锁时,其余未得到锁的线程只能不停的尝试获得锁,而不能中断。这种情况对于大量的竞争线程会造成性能的下降等后果。JDK5以后提供了ReentrantLock的同步机制对于前面提的两种情况有相对的改善。下面我还是写个小例子分析一下:
import java.util.concurrent.locks.ReentrantLock;
/**
* @author: yanxuxin
* @date: 2010-1-4
*/
public class ReentrantLockSample {
public static void main(String[] args) {
testSynchronized();
testReentrantLock();
}
public static void testReentrantLock() {
final SampleSupport1 support = new SampleSupport1();
Thread first = new Thread(new Runnable() {
public void run() {
try {
support.doSomething();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread second = new Thread(new Runnable() {
public void run() {
try {
support.doSomething();
}
catch (InterruptedException e) {
System.out.println("Second Thread Interrupted without executing counter++,beacuse it waits a long time.");
}
}
});
executeTest(first, second);
}
public static void testSynchronized() {
final SampleSupport2 support2 = new SampleSupport2();
Runnable runnable = new Runnable() {
public void run() {
support2.doSomething();
}
};
Thread third = new Thread(runnable);
Thread fourth = new Thread(runnable);
executeTest(third, fourth);
}
/**
* Make thread a run faster than thread b,
* then thread b will be interruted after about 1s.
* @param a
* @param b
*/
public static void executeTest(Thread a, Thread b) {
a.start();
try {
Thread.sleep(100);
b.start(); // The main thread sleep 100ms, and then start the second thread.
Thread.sleep(1000);
// 1s later, the main thread decided not to allow the second thread wait any longer.
b.interrupt();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
abstract class SampleSupport {
protected int counter;
/**
* A simple countdown,it will stop after about 5s.
*/
public void startTheCountdown() {
long currentTime = System.currentTimeMillis();
for (;;) {
long diff = System.currentTimeMillis() - currentTime;
if (diff > 5000) {
break;
}
}
}
}
class SampleSupport1 extends SampleSupport {
private final ReentrantLock lock = new ReentrantLock();
public void doSomething() throws InterruptedException {
lock.lockInterruptibly(); // (1)
System.out.println(Thread.currentThread().getName() + " will execute counter++.");
startTheCountdown();
try {
counter++;
}
finally {
lock.unlock();
}
}
}
class SampleSupport2 extends SampleSupport {
public synchronized void doSomething() {
System.out.println(Thread.currentThread().getName() + " will execute counter++.");
startTheCountdown();
counter++;
}
}
在这个例子中,辅助类SampleSupport提供一个倒计时的功能startTheCountdown(),这里倒计时5s左右。SampleSupport1,SampleSupport2继承其并分别的具有doSomething()方法,任何进入方法的线程会运行5s左右之后counter++然后离开方法释放锁。SampleSupport1是使用ReentrantLock机制,SampleSupport2是使用synchronized机制。
testSynchronized()和testReentrantLock()都分别开启两个线程执行测试方法executeTest(),这个方法会让一个线程先启动,另一个过100ms左右启动,并且隔1s左右试图中断后者。结果正如之前提到的第二点:interrupt()对于synchronized是没有作用的,它依然会等待5s左右获得锁执行counter++;而ReentrantLock机制可以保证在线程还未获得并且试图获得锁时如果发现线程中断,则抛出异常清除中断标记退出竞争。所以testReentrantLock()中second线程不会继续去竞争锁,执行异常内的打印语句后线程运行结束。
这里我是用了ReentrantLock的lockInterruptibly()方法,在SampleSupport1的代码(1)处。这个方法保证了中断线程的响应,如果仅仅是lock()则不会有此功能。但是不管怎么说ReentrantLock提供了解决方案。至于提到的第一点“多条件”的机制我通过java.util.concurrent.ArrayBlockingQueue(源码参考1.6.0.17内的实现)简单的介绍一下:
public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable {
...
/** Main lock guarding all access */
private final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
...
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = (E[]) new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
final E[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
try {
while (count == items.length)
notFull.await();
} catch (InterruptedException ie) {
notFull.signal(); // propagate to non-interrupted thread
throw ie;
}
insert(e);
} finally {
lock.unlock();
}
}
private void insert(E x) {
items[putIndex] = x;
putIndex = inc(putIndex);
++count;
notEmpty.signal();
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
try {
while (count == 0)
notEmpty.await();
} catch (InterruptedException ie) {
notEmpty.signal(); // propagate to non-interrupted thread
throw ie;
}
E x = extract();
return x;
} finally {
lock.unlock();
}
}
private E extract() {
final E[] items = this.items;
E x = items[takeIndex];
items[takeIndex] = null;
takeIndex = inc(takeIndex);
--count;
notFull.signal();
return x;
}
...
}
这里notEmpty和notFull作为lock的两个条件是可以分别负责管理想要加入元素的线程和想要取出元素的线程的wait和notify分别通过await()和signal(),signalAll()方法,有效的分离了不同职责的线程。例如put()方法在元素个数达到最大限制时会使用notFull条件把试图继续插入元素的线程都扔到等待集中,而执行了take()方法时如果顺利进入extract()则会空出空间,这时notFull负责随机的通知被其扔到等待集中的线程执行插入元素的操作。这样的设计使得线程按照功能行为职责管理成为了现实。
通过上述的总结,对于ReentrantLock的优点有了一定的认识,但是它也是实现了与synchronized相同语义和行为的可重用完全互斥锁,所以在竞争机制上不会有什么性能提高,功能倒是强大了不少。不过使用它要配合try{...}finally{...}显式的释放锁,这点是决定如果业务实现没有需要使用其特有的功能,更好的方式是使用synchronized。后者毕竟不用自己去释放锁,降低了开发的失误率。当然在java.util.concurrent.locks包内还一个很有意思的锁:ReentrantReadWriteLock,其提供了部分互斥的锁实现,以后的总结会有介绍。
相关推荐
《ReentrantLock源码详解与应用》 ReentrantLock,可重入锁,是Java并发编程中一个重要的锁实现,它提供了比synchronized更高级别的控制能力,包括公平性和非公平性选择。本文将深入探讨ReentrantLock的原理,特别...
Lock、Synchronized 和 ReentrantLock 的使用 Lock、Synchronized 和 ReentrantLock 是 Java 中三种常用的同步机制,每种机制都有其特点和使用场景。...选择哪种机制取决于具体的应用场景和性能要求。
### ReentrantLock源码分析 #### 一、ReentrantLock简介 ReentrantLock是一个基于...通过对上述流程的分析可以看出,ReentrantLock的设计充分考虑了各种实际应用场景的需求,在保证线程安全的同时,也兼顾了效率问题。
ReentrantLock 实现原理详解 ...在实际应用中,ReentrantLock 可以用于各种需要同步的场景,例如多线程之间的数据共享、线程安全的操作等。ReentrantLock 的使用可以确保线程安全,避免出现死锁和活锁等问题。
在选择使用`synchronized`还是`ReentrantLock`时,应根据具体的应用场景来决定: - 如果需要简单同步操作且不涉及复杂的锁管理,`synchronized`是一个很好的选择。 - 对于需要高级锁管理和更灵活控制的情况,如中断...
在实际应用中,ReentrantLock的灵活性使其成为处理复杂同步场景的理想选择。比如,当需要更细粒度的控制锁,或者需要在释放锁后执行某些操作,或者在等待锁时能够中断或超时,ReentrantLock都能提供解决方案。但是,...
本文将深入探讨Synchronized关键字锁和ReentrantLock锁的异同、功能特性以及它们在实际应用中的适用场景。 首先,Synchronized是一种内置的Java关键字,它提供了简单而强大的线程同步机制。当一个线程进入一个由...
这限制了其在高并发、复杂同步需求场景下的应用。 - **Lock的引入**:JDK 1.5引入了Lock接口及其实现类,如ReentrantLock,它弥补了`synchronized`的不足,支持可中断、可重入和公平/非公平策略,提供了更细粒度的...
本文将深入探讨四种关键的并发控制机制:synchronized关键字、ReentrantLock(可重入锁)、volatile关键字以及Atomic类的原理与应用。 ### 1. synchronized关键字 `synchronized`关键字是Java提供的内置锁,用于...
8. **可选择的锁类型**:根据应用需求,开发人员可以选择使用公平锁还是非公平锁,这在创建ReentrantLock时通过构造函数参数fair指定。 9. **可读写的锁**:虽然ReentrantLock主要关注互斥锁,但Java并发包中还有...
在Java编程中,synchronized和ReentrantLock都是用于实现线程同步的重要工具,它们在并发控制方面扮演着关键角色。然而,两者之间存在一些显著的区别,这些差异体现在功能、灵活性、性能以及使用场景上。 首先,...
首先,`synchronized`是Java语言级别的内置锁,它的使用非常简单,可以直接应用于方法或代码块。当一个线程进入同步代码块或方法时,会获取到对应的监视器锁,其他试图进入的线程将会被阻塞,直到该线程执行完毕并...
根据给定文件的信息,我们可以深入理解AQS(AbstractQueuedSynchronizer)独占锁之ReentrantLock的源码分析及其实现原理。这不仅包括ReentrantLock本身的...理解这些概念和技术,对于开发高性能并发应用具有重要意义。
Java并发编程中,锁是控制...在实际应用中,应根据具体的需求和性能要求来选择合适的锁机制。对于初学者,理解并正确使用`synchronized`是基础,随着经验的增长和需求的复杂化,`ReentrantLock`的特性会显得更为重要。
本文深入探讨了Java并发编程的关键组件——抽象队列同步器(AQS)及其在ReentrantLock的应用。AQS是处理线程同步问题的高效工具,是Java并发编程中的核心。文章首先简要介绍了并发编程领域的先驱Doug Lea。重点在于...
- **应用场景**:适用于实现各种同步组件,如`ReentrantLock`、`Semaphore`等。 #### 2. CLH队列 - **CLH队列**(Craig-Landin-Hagersten队列)是一种非阻塞队列实现,采用单向链表结构。 - **特点**: - 单向...
】args) throws InterruptedException { int threadCount = 4;...了解这两种锁的区别和应用场景对于优化并发程序的性能和线程调度非常重要。在实际开发中,应根据具体需求权衡公平性和性能之间的平衡。
通过对ReentrantLock的源码分析,我们可以更好地理解ReentrantLock的实现机制和使用场景,从而更好地应用于实际开发中。 知识点: 1. ReentrantLock是一个可重入锁,它和synchronized的方法和代码有着相同的行为和...
ReentrantLock 广泛应用于多线程并发编程中,例如: * 数据库连接池的实现 * 线程池的实现 * 并发编程中的同步机制 六、结语 ReentrantLock 是 Java 中的一种高级锁机制,能够在多线程并发编程中发挥重要作用。...
1、本资源包含并发编程基础知识的使用案例,包括:线程创建、Synchronized和Reentrantlock锁的使用、线程安全问题演示、Condition的应用、CountDownLatch的应用、Cyclicbarrier的应用、Semaphore的应用、线程池的...