`
hanhan8020
  • 浏览: 44594 次
  • 性别: Icon_minigender_1
  • 来自: 大连
社区版块
存档分类
最新评论

ReentrantLock 应用

阅读更多

ReentrantLock

文章分类:Java编程

    之前总结了部分无锁机制的多线程基础,理想的状态当然是利用无锁同步解决多线程程序设计的问题。但是实际碰到的问题使得很多情况下,我们不得不借助锁同步来保证线程安全。自从JDK5开始,有两种机制来屏蔽代码块在并行访问的干扰,synchronized关键字已经介绍过了部分内容,所以这次简单的说说另一种锁机制:ReentrantLock。

    对于synchronized的缺点之前也简单的说了一些,实际使用中比较烦扰的几点是:a.只有一个"条件"与锁相关联,这对于大量并发线程的情况是很难管理(等待和唤醒);b.多线程竞争一个锁时,其余未得到锁的线程只能不停的尝试获得锁,而不能中断。这种情况对于大量的竞争线程会造成性能的下降等后果。JDK5以后提供了ReentrantLock的同步机制对于前面提的两种情况有相对的改善。下面我还是写个小例子分析一下:
Java代码 复制代码
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内的实现)简单的介绍一下:
Java代码 复制代码
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源码的使用问题详解.docx

    什么是公平锁和非公平锁 公平与非公平的一个很本质的区别就是,是否遵守FIFO(也就是先来后到)。当有多个线程来申请锁的时候,是否先申请的线程先获取锁,后申请的线程后获取锁?如果是的,则是 公平锁 ,否则是...

    Java中ReentrantLock的使用.docx

    可重入锁: 也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,...ReentrantLock 在Java也是一个基础的锁,ReentrantLock 实现Lock接口提供一系列的基础函数,开发人员可以灵活的是应用函数满足各种复杂多变应用场景;

    7、深入理解AQS独占锁之ReentrantLock源码分析(1).pdf

    6.JUC并发工具类在大厂的应用场景详解 (1).pdf 7、深入理解 AQS 独占锁之 Reentrantlock 源码分析 (1).pdf 8、读写锁ReentrantReadWriteLock&StampLock详解.pdf 9、并发容器 (Map、List、Set) 实战及其原理.pdf 10...

    Java并发编程相关技术使用案例

    1、本资源包含并发编程基础知识的使用案例,包括:线程创建、Synchronized和Reentrantlock锁的使用、线程安全问题演示、Condition的应用、CountDownLatch的应用、Cyclicbarrier的应用、Semaphore的应用、线程池的...

    【并发编程】简单化理解AQS和ReentrantLock.pdf

    实际案例:通过实际的编程案例来展示并发编程的应用。 适合的人群 初学者:对计算机科学或编程有一定了解,但尚未接触过并发编程的开发者。 中级开发者:已经具备一定的编程经验,希望提高程序性能或学习多线程开发...

    6、JUC并发工具类在大厂的应用场景详解(1).pdf

    6.JUC并发工具类在大厂的应用场景详解 (1).pdf 7、深入理解 AQS 独占锁之 Reentrantlock 源码分析 (1).pdf 8、读写锁ReentrantReadWriteLock&StampLock详解.pdf 9、并发容器 (Map、List、Set) 实战及其原理.pdf 10...

    java并发编程1-9

    java并发编程1-9,可解压,并发编程必看资料。 1 Java 并发编程实践基础 2 构建线程安全应用程序 3 使用JDK 并发包构建程序 4 使用开源软件 Amino ...7 显示锁 ReentrantLock 8 原子变量与非阻塞算法 9 Java 内存模型

    Java-Interview:此项目为 Java 面试的汇总,多数是一些 Java 基础知识、底层原理、算法详解。也有上层应用设计,其中不乏一些大厂面试真题

    也有上层应用设计,其中不乏一些大厂面试真题。 如果对你有帮助请点下 Star,有疑问欢迎提 ,有好的想法请提 。 常用集合 ArrayList/Vector LinkedList HashMap HashSet LinkedHashMap Java 多线程 多线程中的常见...

    10、阻塞队列BlockingQueue实战及其原理分析.pdf

    6.JUC并发工具类在大厂的应用场景详解 (1).pdf 7、深入理解 AQS 独占锁之 Reentrantlock 源码分析 (1).pdf 8、读写锁ReentrantReadWriteLock&StampLock详解.pdf 9、并发容器 (Map、List、Set) 实战及其原理.pdf 10...

    9、并发容器(Map、List、Set)实战及其原理.pdf

    6.JUC并发工具类在大厂的应用场景详解 (1).pdf 7、深入理解 AQS 独占锁之 Reentrantlock 源码分析 (1).pdf 8、读写锁ReentrantReadWriteLock&StampLock详解.pdf 9、并发容器 (Map、List、Set) 实战及其原理.pdf 10...

    8、读写锁ReentrantReadWriteLock&StampLock详解.pdf

    6.JUC并发工具类在大厂的应用场景详解 (1).pdf 7、深入理解 AQS 独占锁之 Reentrantlock 源码分析 (1).pdf 8、读写锁ReentrantReadWriteLock&StampLock详解.pdf 9、并发容器 (Map、List、Set) 实战及其原理.pdf 10...

    Java面试题.docx

    51、ReentrantLock 、synchronized和volatile比较 53、死锁的四个必要条件? 56、什么是线程池,如何使用? 56、什么是线程池,如何使用? 58、有三个线程T1,T2,T3,怎么确保它们按顺序执行?

    Java并发包源码分析(JDK1.8)

    Java并发包源码分析(JDK1.8):囊括了java.util.concurrent包中大部分类的源码分析,其中涉及automic包,locks包(AbstractQueuedSynchronizer、ReentrantLock、ReentrantReadWriteLock、LockSupport等),queue...

    java-interview

    也有上层应用设计,其中不乏一些大厂面试真题。 如果对你有帮助请点下 Star,有疑问欢迎提 ,有好的想法请提 。 常用集合 ArrayList/Vector LinkedList HashMap HashSet LinkedHashMap Java 多线程 多线程中的常见...

    Java-Interview:https

    也有上层应用设计,其中不乏一些大厂面试真题。 如果对你有帮助请点下 Star,有疑问欢迎提 ,有好的想法请提 。 常用集合 HashMap HashSet LinkedHashMap Java 多线程 多线程中的常见问题 synchronize 关键字原理 多...

    Java并发编程实战

    介绍了如何使用类库提供的基本并发构建块,用于避免并发危险、构造线程安全的类及验证线程安全的规则,如何将小的线程安全类组合成更大的线程安全类,如何利用线程来提高并发应用程序的吞吐量,如何识别可并行执行的...

    java7源码-scn:《疯狂Java讲义》学习

    加入多线程:实现简单的命令行界面C/S聊天室应用 2017/07/14: 1.多线程 线程和进程的概念 线程的创建和启动: 继承Thread类创建线程类(共享线程类实例变量)、 实现Runnable接口创建线程类(共享线程类的实例变量)、 ...

    java8源码-thread-concurrent-study:线程并发研究

    美团技术团队《从ReentrantLock的实现看AQS的原理及应用》 测试 老钱《打通Java任督二脉--并发数据结构的基石》 HongJie《一行一行源码分析清除AbstractQueuedSynchronizer》 爱吃鱼的KK ...

    javaSE代码实例

    17.3.1 Lock接口与ReentrantLock类简介 386 17.3.2 ReentrantLock锁的具体使用 387 17.3.3 ReadWriteLock接口与ReentrantReadWriteLock类简介 390 17.3.4 ReentrantReadWriteLock读/写锁的具体使用 391 17.4...

    sesvc.exe 阿萨德

    codeceo 首页问答热门文章RSS订阅 文章首页 Java JavaScript ... iOS ...2018-07-25 分类:JAVA开发、编程开发、首页精华0人评论 来源:crossoverjie.top ...Map 这样的 Key Value 在软件开发中是非常经典的结构,常...

Global site tag (gtag.js) - Google Analytics