`

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多线程系列(四)ReentrantLock的使用.docx

    ReentrantLock类可以唤醒指定条件的线程,而object的唤醒是随机的 Condition类和Object类 Condition类的awiat方法和Object类的wait方法等效 Condition类的signal方法和Object类的notify方法等效 Condition类...

    Java多线程ReentrantLock1

    Java多线程ReentrantLock1

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

    Java多线程并发的程序中使用互斥锁有synchronized和ReentrantLock两种方式,这里我们来详解Java多线程编程中互斥锁ReentrantLock类的用法:

    Java多线程之ReentrantLock与Condition - 平凡希 - 博客园1

    1、ReentrantLock简介 2、ReentrantLock函数列表 3、重入的实现 4、公平锁与非公平锁 5、ReentrantLock 扩展的功能 6

    java多线程每个线程挨着打印ABC的4种实现方式

    java多线程每个线程挨着打印ABC的4种实现方式,有4个线程t1、t2、t3、t4,t1打印A后t2打印A再t3打印A再t4打印A,然后从新回到t1打印B再t2打印B...t4打印B... 4个线程轮流打印abc... 一个线程可以理解为一个人,打印...

    java多线程安全性基础介绍.pptx

    java多线程安全性基础介绍 线程安全 正确性 什么是线程安全性 原子性 竞态条件 i++ 读i ++ 值写回i 可见性 JMM 由于cpu和内存加载速度的差距,在两者之间增加了多级缓存导致,内存并不能直接对cpu可见。 ...

    Java 多线程与并发(11-26)-JUC锁- ReentrantLock详解.pdf

    Java 多线程与并发(11_26)-JUC锁_ ReentrantLock详解

    个人总结的深入java多线程开发

    看完《think in java》多线程章节,自己写的多线程文档,还结合了其他的相关网络资料。 线程 一. 线程池 1)为什么要使用线程池 2 2)一个具有线程池的工作队列 3 3)使用线程池的风险: 4 4)有效使用线程池的原则 5...

    Java多线程 ReentrantLock互斥锁详解

    主要介绍了Java多线程 ReentrantLock互斥锁详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

    2023年最新Java高并发多线程面试题

    适用人群:适合想了解或学习高并发多线程的 IT 学生、开发人员、研究人员以及使用Java多线程进行开发的任何人。 能学到什么:常见Java高并发多线程面试问题及在相关场景下如何处理和解决这些问题。 阅读建议:通过...

    Java多线程中ReentrantLock与Condition详解

    主要介绍了Java多线程中ReentrantLock与Condition详解,需要的朋友可以参考下

    locks框架_ReentrantLock.pdf

    Locks 框架概述: 简要介绍 Locks 框架,解释其在多线程编程中的作用和优势。比较 Locks 框架与传统 synchronized 关键字的不同之处。 ReentrantLock 简介: 详细讲解 ReentrantLock 的概念和特点。解释为什么它被...

    Java Core Sprout:基础、并发、算法

    Java多线程 多线程中的常见问题 同步关键字原理 多线程的三大核心 对锁的一些认知 ReentrantLock实现原理 ConcurrentHashMap 的实现原理 如何优雅地使用和理解线程池 深入理解线程通信 一个线程召集的诡异事件 ...

    详解java多线程的同步控制

    目录线程安全 Thread Safety重入锁 ReentrantLock读写锁 ReadWriteLock倒计数器 CountDownLatch循环栅栏 CyclicBarrier信号量 Semaphore 线程安全 Thread Safety JMM JMM(Java Memory Model)是一种基于计算机内存...

    Java多线程源码笔记.pdf

    1.什么是多线程 2.Thread类解析 3.使用多线程需要注意的问题 4.synchronized锁和lock锁 5.AQS 6.ReentrantLock和ReentrantReadWriteLock 7.线程池 8.死锁 9.线程常用的工具栏 10.Atomic 11.ThreadLocal

    java高并发相关知识点.docx

    线程:Java多线程的实现方式,包括继承Thread类和实现Runnable接口。 锁:Java中的锁机制,包括synchronized关键字和ReentrantLock类。 线程池:Java中的线程池机制,包括线程池的创建、执行任务、关闭等操作。 并发...

    多线程(11)ReentrantLock公平锁与非公平锁(修改)1

    private static class MyReentrantLock extends ReentrantLock {public MyReentrantLo

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

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

    【2018最新最详细】并发多线程教程

    【2018最新最详细】并发多线程教程,课程结构如下 1.并发编程的优缺点 2.线程的状态转换以及基本操作 3.java内存模型以及happens-before规则 4.彻底理解synchronized 5.彻底理解volatile 6.你以为你真的了解final吗...

    java8集合源码分析-Notes:笔记

    多线程:多线程生成的原因(Java内存模型与i++操作解析)] [Java 多线程:生产者消费者问题] [Java 多线程:synchronized 关键字(修饰类,方法,静态方法,代码块)] [Java 多线程:Lock 接口(接口方法分析,...

Global site tag (gtag.js) - Google Analytics