`

Java多线程基础总结七:ReentrantLock

阅读更多
   之前总结了部分无锁机制的多线程基础,理想的状态当然是利用无锁同步解决多线程程序设计的问题。但是实际碰到的问题使得很多情 况下,我们不得不借助锁同步来保证线程安全。自从JDK5开始,有两种机制来屏蔽代码块在并行访问的干扰,synchronized关键字已经介绍 过了部分内容,所以这次简单的说说另一种锁机制:ReentrantLock。
   对于synchronized的缺点之前也简单的说了一些,实际使用中比较烦扰的几点是:a.只有一个"条件"与锁相关联,这对于大量并发线程 的情况是很难管理(等待和唤醒);b.多线程竞争一个锁时,其余未得到锁的线程只能不停的尝试获得锁,而不能中断。这种情况对于大量的 竞争线程会造成性能的下降等后果。JDK5以后提供了ReentrantLock的同步机制对于前面提的两种情况有相对的改善。下面我还是写个小例 子分析一下:

package thread;

import java.util.concurrent.locks.ReentrantLock;

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,其提供了部分互斥的锁实现,以后的总结会有介绍。


ref:http://www.bianceng.cn/Programming/Java/201206/34155.htm
分享到:
评论

相关推荐

    Java_多线程编程线程:大总结

    ### Java多线程编程大总结 #### 一、Java多线程编程的演进与重要性 Java多线程编程自Java 5之后经历了显著的革新与扩展,引入了强大的并发包,极大地增强了多线程编程的能力。在Java 5之前,多线程的支持较为薄弱...

    java多线程系列(四)ReentrantLock的使用.docx

    Java中的多线程编程在处理并发问题时是至关重要的,特别是在高并发环境下,对资源的精确控制成为提高系统效率的关键。本篇文章将深入探讨`ReentrantLock`的使用,它是Java并发包`java.util.concurrent.locks`中的一...

    Java多线程编程总结

    ### Java多线程编程总结 #### 一、Java线程:概念与原理 1. **操作系统中线程和进程的概念** - 当前的操作系统通常为多任务操作系统,多线程是实现多任务的一种手段。 - **进程**:指内存中运行的应用程序,每个...

    JAVAJAVA多线程教学演示系统论文

    《JAVA多线程教学演示系统》是一篇深入探讨JAVA多线程编程的论文,它针对教育领域中的教学需求,提供了一种生动、直观的演示方式,帮助学生更好地理解和掌握多线程技术。这篇论文的核心内容可能包括以下几个方面: ...

    Java 多线程学习总结归纳(附代码)

    以上内容涵盖了Java多线程的基础知识,包括创建、同步、终止、线程安全和并发控制等方面。通过实际的代码实践,可以深入理解并掌握这些概念,提升多线程编程的能力。同时,文档中的代码示例能帮助读者更好地理解和...

    Java多线程的总结

    Java多线程是Java编程中的一个核心概念,它在现代软件开发中扮演着至关重要的角色。多线程允许程序同时执行多个任务,提高了系统资源的利用率,提升了应用程序的响应速度和并发性能。对于大型分布式系统、Web应用...

    深入学习:Java多线程编程

    《深入学习:Java多线程编程》是一本...通过学习《深入学习:Java多线程编程》,开发者可以提升在复杂并发环境下的编程能力,为开发高并发、高可用的应用打下坚实的基础。这本书无疑是Java开发者进阶必备的参考书之一。

    java多线程Demo

    Java多线程是Java编程中的一个重要概念,它允许程序同时执行多个任务,提高了程序的效率和响应速度。在Java中,实现多线程有两种主要方式:继承Thread类和实现Runnable接口。 1. 继承Thread类: 当我们创建一个新...

    JAVA多线程总结

    本篇总结涵盖了Java多线程的基础概念、创建与启动、线程调度、同步与协作以及新特性。 **一、Java线程:概念与原理** 1. **线程与进程**: - **进程**:是操作系统资源分配的基本单位,每个进程都有独立的内存...

    详解Java多线程编程中互斥锁ReentrantLock类的用法

    在Java多线程编程中,ReentrantLock提供了更灵活的锁管理机制,相比synchronized,它允许更细粒度的控制,并且提供了更多的同步原语。以下是一些使用ReentrantLock的关键点: 1. **可重入性**:ReentrantLock允许一...

    java多线程经典案例

    通过分析并实践`threadTest`案例,我们可以深入理解Java多线程的原理和使用技巧,为编写高效并发程序打下坚实基础。同时,也要注意多线程编程中的死锁、活锁和饥饿等问题,合理设计线程间的交互,避免出现不可预期的...

    Java多线程编程核心技术_完整版_java_

    Java多线程编程是Java开发中的重要组成部分,它允许程序同时执行多个任务,极大地提高了程序的效率和响应性。在Java中,多线程主要通过继承Thread类或实现Runnable接口来实现。本教程《Java多线程编程核心技术》将...

    java多线程进阶

    1. **线程基础**:书中首先会介绍Java多线程的基础知识,包括线程的创建方式(如通过`Thread`类或实现`Runnable`接口)、线程的生命周期(新建、就绪、运行、阻塞和死亡),以及如何启动和停止线程。 2. **线程同步...

    java多线程文件传输

    Java多线程文件传输是Java编程中一个重要的实践领域,特别是在大数据处理、网络通信和分布式系统中。在Java中,多线程可以提高程序的执行效率,尤其在处理并发任务时,如大文件的上传、下载和传输。下面将详细探讨...

    java多线程编程大总结

    Java多线程编程是Java语言中的一个重要组成部分,它允许程序在单个进程内同时执行多个线程,从而实现多任务处理。在Java 5版本之前,Java对多线程的支持相对有限,编写复杂的多线程程序具有一定的挑战性。然而,从...

    Java多线程干货系列(1)Java多线程基础编程开发技术

    本系列的第1部分将重点介绍Java多线程的基础编程开发技术,旨在帮助开发者掌握多线程的核心概念和实用技巧。 1. **线程的概念**: 线程是操作系统调度的基本单位,它代表了程序执行的流程。在一个进程中可以有多个...

    Java多线程的经典资料.rar

    Java多线程是Java编程中的核心概念,它允许程序同时执行多个任务,极大地提升了软件的效率和并发性。这份“Java多线程的经典资料.rar”压缩包包含了一份名为“Java线程.pdf”的文档,很可能是关于Java多线程的详细...

Global site tag (gtag.js) - Google Analytics