`
QING____
  • 浏览: 2253277 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Java并发锁控制API详解

 
阅读更多

一.Lock接口(java.util.concurrent.locks):

  • void lock():获取锁,阻塞方式;如果资源已被其他线程锁定,那么lock将会阻塞直到获取锁,锁阻塞期间不受线程的Interrupt的影响,在获取锁成功后,才会检测线程的interrupt状态,如果interrupt=true,则抛出异常。
  • unlock():释放锁
  • tryLock():尝试获取锁,并发环境中"闯入"行为,如果有锁可用,直接获取锁并返回true,否则范围false.
  • lockInterruptibly():尝试获取锁,并支持"中断"请求。与lock的区别时,此方法的开始、结束和执行过程中,都会不断检测线程的interrupt状态,如果线程被中断,则立即抛出异常;而不像lock方法那样只会在获取锁之后才检测。

二.Lock接口实现类

    Lock直接实现,只有3个类:ReentrantLock和WriteLock/ReadLock;这三种锁;Lock和java的synchronized(内置锁)的功能一致,均为排他锁.

 

    ReentrantLock为重入排他锁,对于同一线程,如果它已经持有了锁,那么将不会再次获取锁,而直接可以使用.

    ReentrantReadWriteLock并没有继承ReentrantLock,而是一个基于Lock接口的单独实现.它实现了                 ReadWriteLock,即读写分离锁,是一种采用锁分离技巧的API.

 

    尽管在API级别ReentrantReadWriteLock和ReentrantLock没有直接继承关系,但是ReentrantReadWriteLock中的ReadLock和WriteLock都具有ReentrantLock的全部语义(简单说,就是把ReentrantLock的代码copy了一下.),即锁的可重入性.WriteLock支持Condition(条件),ReadLock不支持.

 

    Lock的实现类中,都包含了2中锁等待策略:公平和非公平;其实他们的实现也非常简单,底层都是使用了queue来维持锁请求顺序.[参考:http://shift-alt-ctrl.iteye.com/blog/1839142]

 

    公平锁,就是任何锁请求,首先将请求加入队列,然后再有队列机制来决定,是阻塞还是分配锁.

非公平,就是允许"闯入",当然公平锁,也无法干扰"闯入",对于任何锁请求,首先检测锁状态是否可用,如果可用直接获取,否则加入队列..

 

    ReentrantLock本质上和synchronized修饰词是同一语义,如果一个线程lock()之后,其他线程进行lock时必须阻塞,直到当前线程的前续线程unlock.[执行lock操作时,将会被队列化(假如在公平模式下),获取lock的线程都将具有前续/后继线程,前续线程就是当前线程之前执行lock操作而阻塞的线程,后继线程就是当前线程之后执行lock操作的线程;那么对于unlock操作就是"解锁"信号的传递,如果当前线程unlock,那么将会触发后继线程被"唤醒",即它因为lock操作阻塞状态被解除.];这是ReentrantLock的基本原理,但是当ReentrantLock在Conditon情况下,事情就变得更加复杂.[参加下述]

 

三.Condition:锁条件

    Condition与Lock形成happen-before关系。Condition将Object的监视器方法(wait,notify,notifyAll)分解成截然不同的对象,以便通过这些对象与任意Lock实现组合。使Lock具有等待“集合”的特性,或者“类型”;Lock替代了synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。(synchronized + object.wait对应Lock + Condition.await)

 

    Condition又称条件队列,为线程提供了一个含义,以便在某种状态条件现在可能为true的其他线程通知它之前,一直挂起该线程。即多个线程,其中一个线程因为某个条件而阻塞,其他线程当“条件”满足时,则“通知”哪些阻塞的线程。这,几乎和object中wait和notify的机制一样。

    Condition和wait一样,阻塞时也将原子性的释放锁(间接执行了release()方法)。并挂起线程。Condition必须与Lock形成关系,只有获取lock权限的,才能进行Condition操作。Condition底层基于AQS实现,条件阻塞,将以队列的方式,LockSupport支持。其实现类有ConditionObject,这也是Lock.newCondition()的返回实际类型,在等待 Condition 时,允许发生“虚假唤醒”,这通常作为对基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,因为 Condition 应该总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。

  • void await() throws InterruptedException:当前线程阻塞,并原子性释放对象锁。如下条件将触发线程唤醒:
  1. 当线程被中断(支持中断响应),
  2. 其他线程通过condition.signal()方法,且碰巧选中当前线程唤醒
  3. 其他线程通过condition.signalAll()方法
  4. 发生虚假唤醒

    底层实现,await()方法将当前线程信息添加到Conditon内部维护的"await"线程队列的尾部(此队列的目的就是为singal方法保持亟待唤醒的线程的顺序),然后释放锁(执行tryRelease()方法,注意此处释放锁,仅仅是释放了锁信号,并不是unlock,此时其他线程仍不能获取锁--lock方法阻塞),然后使用LockSupport.park(this)来强制剥夺当前线程执行权限。await方法会校验线程的中断标记。

由此可见,await()方法执行之后,因为已经"归还"了锁信号,那么其他线程此时执行lock方法,将不再阻塞..

 

  • void awaitUninterruptibly():阻塞,直到被唤醒。此方法不响应线程中断请求。即当线程被中断时,它将继续等待,直到接收到signal信号(你应该能想到"陷阱"),当最终从此方法返回时,仍然将设置其中断状态。
  • void signal()/signalAll():唤醒一个/全部await的线程。
    对于signal()方法而言,底层实现为,遍历await"线程队列,找出此condition上最先阻塞的线程,并将此阻塞线程unpark.至此为止,我们似乎发现"锁信号"丢失了,因为在线程await时通过tryRelease时释放了一次信号.那么被signal成功的线程,首先执行一次acquire(增加锁信号),然后校验自己是否被interrupted,如果锁信号获取成功且线程状态正常,此时才正常的从await()方法退出.经过这么复杂的分析,终于明白了ReentrantLock + Condition情况下,锁状态变更和线程控制的来龙去脉...
//////例子:
private Lock lock = new ReentrantLock();
private Condition full = lock.newCondition();
private Condition empty = lock.newCondition();
public Object take(){
	lock.lock();
	try{
		while(isEmpty()){
			empty.await()
		}
		Object o = get()
		full.signalAll();
		return o;
	}finally{
		lock.unlock();
	}
}

public void put(Object o){
	lock.lock();
	try{
		while(isFull()){
			full.await();
		}
		put(o);
		empty.signalAll();
	}finally{
		lock.unlock();
	}
}
 

四.机制

    Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。注意,Lock 实例只是普通的对象,其本身可以在 synchronized 语句中作为目标使用。获取 Lock 实例的监视器锁与调用该实例的任何 lock() 方法没有特别的关系。为了避免混淆,建议除了在其自身的实现中之外,决不要以这种方式使用 Lock 实例。

 

    Lock接口具有的方法:

  • void lock():获取锁,阻塞直到获取。
  • void lockInterruptibly() throws InterrutedException:获取锁,阻塞直到获取成功,支持中断响应。
  • boolean tryLock():尝试获取锁,返回是否获取的结果。如果碰巧获取成功,则返回true,此时已经持有锁。
  • boolean tryLock(long time,TimeUnit) throws InterruptedException:尝试获取锁,获取成功返回true,超时时且没有获取锁则返回false。
  • void unlock():释放锁。约定只有持有锁者才能释放锁,否则抛出异常。
  • void newCondition():返回绑定到lock的条件。

 

五.ReadWriteLock

    ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer(写锁),读取锁可以由多个 reader 线程同时保持(共享锁)。写入锁是独占的。所有 ReadWriteLock 实现都必须保证 writeLock 操作的内存同步效果也要保持与相关 readLock 的联系。也就是说,成功获取读锁的线程会看到写入锁之前版本所做的所有更新。

    与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程),读-写锁利用了这一点。从理论上讲,与互斥锁相比,使用读-写锁所允许的并发性增强将带来更大的性能提高。在实践中,只有在多处理器上并且只在访问模式适用于共享数据时,才能完全实现并发性增强。

  • Lock readLock():返回读锁。
  • Lock writeLock():返回写锁。

六.ReentrantLock

    ReentrantLock,重入排它锁,它和synchronized具有相同的语义以及在监视器上具有相同的行为,但是功能更加强大。

    ReetrantLock将由最近成功获得锁且还没有释放锁的线程标记为“锁占有者”;当锁没有被线程持有时,调用lock方法将会成功获取锁并返回,如果当前线程为锁持有者,再次调用lock将立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查此情况是否发生。

 

    ReentrantLock的构造方法,允许接收一个“公平策略”参数,“公平策略”下,多个线程竞争获取锁时,将会以队列化锁请求者,并将锁授予队列的head。在“非公平策略”下,则不完全保证锁获取的顺序,允许闯入行为(tryLock)。

    ReentrantLock基于AQS机制,锁信号量为1,如果信号量为1且当前锁持有者不为自己,则不能获取锁。释放锁时,如果当前锁持有者不是自己,也将抛出“IllegalMonitorStateException”。由此可见,对于ReentrantLock,lock和release方法是需要组合出现。

 

七.ReentrantReadWriteLock:可重入读写分离锁

  1.  重入性 :当前线程可以重新获取相应的“读锁”或者“写锁”,在写入线程保持的所有写入锁都已经释放后,才允许重入reader(读取线程)使用它们。writer线程可以获取读锁,但是reader线程却不能直接获取写锁。
  2. 锁降级:重入还允许写入锁降级为读锁,其实现方式为:先获取写入锁,然后获取读取锁,最后释放写入锁。但是读取锁不能升级为写入锁。
  3. Conditon的支持:只有写入锁支持conditon,对于读取锁,newConditon方法直接抛出UnsupportedOperationException。

    ReentrantReadWriteLock目前在java api中无直接使用。ReentrantReadWriteLock并没有继承自     ReentrantLock,而是单独重新实现。其内部仍然支持“公平性”“非公平性”策略。

    ReentrantReadWriteLock基于AQS,但是AQS只有一个state来表示锁的状态,所以如果一个state表示2种类型的锁状态,它做了一个很简单的策略,“位运算”,将一个int类型的state拆分为2个16位段,左端表示readlock锁引用计数,右端16位表示write锁。在readLock、writeLock进行获取锁或者释放锁时,均是通过有效的位运算和位控制,来达到预期的效果。

 

八.ReadLock

 

  • void lock():获取读取锁,伪代码如下: 
//如果当前已经有“写锁”,且持有写锁者不是当前线程(如果是当前线程,则支持写锁,降级为读锁),则获取锁失败
//即任何读锁的获取,必须等待队列中的写锁释放
//c为实际锁引用量(exclusiveCount方法实现为:c & ((1<<16) -1)
if (exclusiveCount(c) != 0 &&getExclusiveOwnerThread() != current)
   return -1;
 //CAS操作,操作state的左端16位。
if(CAS(c,c + (1<<16))){
	return 1;
}

 

  • void unlock():释放read锁,即共享锁,伪代码如下:
//CAS锁引用
for (;;) {
	int c = getState();
	int nextc = c - (1<<16);//位操作,释放一个锁。
if (compareAndSetState(c, nextc))
	return nextc == 0;
}
 

九.WriteLock

  • void lock():获取写入锁,伪代码如下:

 

//当前线程
Thread current = Thread.currentThread();
//实际的锁引用state
int c = getState();
//右端16位,通过位运算获取“写入锁”的state
int w = exclusiveCount(c);
//如果有锁引用
if (c != 0) {
	//且所引用不是自己
	if (w == 0 || current != getExclusiveOwnerThread()){
		return false;
	}
}

//如果写入锁state为0,且CAS成功,则设置state和独占线程信息
if ((w == 0 && writerShouldBlock(current)) ||!compareAndSetState(c, c + acquires)){
	return false;
}
setExclusiveOwnerThread(current);
return true;

 

  • void unlock():释放写入锁,伪代码如下:

 

//计算释放锁的信号量
int nextc = getState() - releases;
//对于写入锁,则校验当前线程是否为锁持有者,否则不可以释放(死锁)
if (Thread.currentThread() != getExclusiveOwnerThread())
	throw new IllegalMonitorStateException();
//释放锁,且重置独占线程信息
if (exclusiveCount(nextc) == 0) {
	setExclusiveOwnerThread(null);
	setState(nextc);
	return true;
} else {
	setState(nextc);
	return false;
}

 

十.LockSupport:用来创建锁和其他同步类的基本线程阻塞原语。

    底层基于hotspot的实现unsafe。park 和 unpark 方法提供了阻塞和解除阻塞线程的有效方法。三种形式的 park(即park,parkNanos(Object blocker,long nanos),parkUntil(Object blocker,long timestamp)) 还各自支持一个 blocker 对象参数。此对象在线程受阻塞时被记录,以允许监视工具和诊断工具确定线程受阻塞的原因。(这样的工具可以使用方法 getBlocker(java.lang.Thread) 访问 blocker。)建议最好使用这些形式,而不是不带此参数的原始形式。

    在锁实现中提供的作为 blocker 的普通参数是 this。

  • static void park(Object blocker):阻塞当前线程,直到如下情况发生:
  1. 其他线程,调用unpark方法,并将此线程作为目标而唤醒
  2. 其他线程中断当前线程此方法不报告,此线程是何种原因被放回,需要调用者重新检测,而且此方法也经常在while循环中执行 
while(//condition,such as:queue.isEmpty){
	LockSupport.park(queue);//此时queue对象作为“阻塞”点传入,以便其他监控工具查看,queue的状态
	//检测当前线程是否已经中断。
	if(Thread.interrupted()){
		break;
	}
}

 

  • void getBlocker(Thread t):返回提供最近一次尚未解除阻塞的park的阻塞点。可以返回null。
  • void unpark(Thread t):解除指定线程阻塞,使其可用。参数null则无效果。

     LockSupport实例(不过不建议在实际代码中直接使用LockSupport,很多时候,你可以使用锁来控制):

 

/////////////Demo

public class LockSupportTestMain {

	/**

	* @param args

	*/

	public static void main(String[] args) throws Exception{
		System.out.println("Hear!");
		BlockerObject blocker = new BlockerObject();
		LThread tp = new LThread(blocker, false);
		LThread tt = new LThread(blocker, true);
		tp.start();
		tt.start();
		Thread.sleep(1000);
	}

	static class LThread extends Thread{
		private BlockerObject blocker;
		boolean take;
		LThread(BlockerObject blocker,boolean take){
			this.blocker = blocker;
			this.take = take;
		}

		@Override
		public void run(){
			if(take){
			while(true){
				Object o = blocker.take();
				if(o != null){
					System.out.println(o.toString());
				}
				}
				}else{
					Object o = new Object();
					System.out.println("put,,," + o.toString());
					blocker.put(o);
				}
		}
	}

	static class BlockerObject{
		Queue<Object> inner = new LinkedList<Object>();
		Queue<Thread> twaiters = new LinkedList<Thread>();
		Queue<Thread> pwaiters = new LinkedList<Thread>();
		public void put(Object o){
			inner.offer(o);
			pwaiters.offer(Thread.currentThread());
			Thread t = twaiters.poll();
			if(t != null){
				LockSupport.unpark(t);
			}
			System.out.println("park");
			LockSupport.park(Thread.currentThread());
			System.out.println("park is over");
		}

		public Object take(){
			Thread t = pwaiters.poll();
			if(t != null){
				System.out.println("unpark");
				LockSupport.unpark(t);
				System.out.println("unpark is OK");
			}
				//twaiters.offer(Thread.currentThread());
				return inner.poll();
			}
		}

}

 

 

备注:有时候会疑惑wait()/notify() 和Unsafe.park()/unpark()有什么区别?区别是wait和notify是Object类的方法,它们首选需要获得“对象锁”,并在synchronized同步快中执行。park和unpark怎不需要这么做。wait和park都是有当前线程发起,notify和unpark都是其他线程发起。wait针对的是对象锁,park针对的线程本身,但是最终的效果都是导致当前线程阻塞。Unsafe不建议开发者直接使用。

分享到:
评论

相关推荐

    13-Java并发编程学习宝典.zip

    Java并发编程是软件开发中的重要领域,特别是在大型系统和高并发场景中不可或缺。"13-Java并发编程学习宝典.zip" 包含了一系列关于Java并发编程的学习资源,旨在帮助开发者掌握多线程编程的核心技术和最佳实践。以下...

    java高并发程序设计(原版电子书)

    1. **并发基础**:首先,书中会介绍并发编程的基本概念,如线程、进程、同步与通信机制,以及Java中的线程API,如`Thread`类和`Runnable`接口。 2. **线程管理**:讨论如何创建、启动、停止和控制线程,包括线程池...

    Java_NIO_API详解

    ### Java NIO API详解 #### 一、引言 在Java早期版本中,I/O操作主要依赖于`java.io`包中的流式API,这些API虽然简单易用,但其本质是阻塞式的,这意味着每次读写操作都会等待直至完成。这种机制在处理大量并发...

    Java并发编程的艺术

    #### 八、Java并发工具类详解 第八章关注于Java提供的并发工具类,这些工具类包括CountDownLatch、CyclicBarrier、Semaphore等,它们是Java并发编程库的重要组成部分。本章不仅详细介绍了这些工具类的功能和用途,...

    阿里专家级并发编程架构师课程 彻底解决JAVA并发编程疑难杂症 JAVA并发编程高级教程

    阿里专家级并发编程架构师级课程,完成课程的学习可以帮助同学们解决非常多的JAVA并发编程疑难杂症,极大的提高JAVA并发编程的效率。课程内容包括了JAVA手写线程池,UC线程池API详解,线程安全根因详解,锁与原子类...

    java并发包api详解.txt

    java.util.concurrent包提供了创建并发应用程序的工具,本资源主要是对该api进行详细的解读,并对api的使用做出安全高效的引用建议.

    Java并发编程:设计原则与模式(第二版).rar

    2. **Java并发API**:Java提供了丰富的并发API,如`Thread`、`Runnable`、`ExecutorService`和`Future`等。这些API是Java并发编程的核心,书中会详细解析它们的使用方法和应用场景。 3. **线程安全**:线程安全是...

    Java锁机制详解.pdf

    Java锁机制是Java多线程编程中的核心概念之一,其主要...此外,Java并发API中还包括了其他并发工具类,如Semaphore、CountDownLatch以及CyclicBarrier等,这些工具类在设计并发控制逻辑时,提供了更多的灵活性和功能。

    Windows API开发详解

    7. **并发编程**:理解Java的线程模型,包括锁、同步、原子性操作、线程池等概念,以及Concurrent包下的高级工具。 8. **JVM参数调整**:学习如何根据应用需求,设置和调整JVM参数,以优化程序性能。 9. **异常...

    SSM实战项目——Java高并发秒杀API,详细流程+学习笔记.zip

    【SSM实战项目——Java高并发秒杀API详解】 在这个Java实战项目中,我们主要探讨的是如何使用Spring、Spring MVC和MyBatis(SSM)框架来构建一个高并发的秒杀系统。这个项目旨在帮助开发者理解在处理大量并发请求时...

    java.nio API详解

    Java NIO(New Input/Output)API是在JDK 1.4版本中引入的一个重要的改进,它是对传统Java IO API的补充,旨在提供更高效、更灵活的数据输入和输出方式,特别是对于高并发和高性能的应用场景,如服务器端程序。...

    Java_NIO_API详解[参照].pdf

    Java NIO(New Input/Output)API是在JDK 1.4版本中引入的一个重要的功能扩展,它提供了与传统IO(基于流的IO)不同的I/O操作方式,特别是在处理高并发、高性能的服务端应用程序中,NIO具有显著的优势。传统的IO基于...

    Tomcat与Java_Web开发技术详解3.pdf

    ### Tomcat与Java Web开发技术详解 #### 一、Tomcat简介 Tomcat是一款开源的Servlet容器,由Apache软件基金会下属的Jakarta项目开发。它实现了对Servlet和JavaServer Pages (JSP)的支持,可以作为独立的应用服务器...

    Java高并发编程详解:多线程与架构设计 (Java核心技术系列)

    这一部分的学习对于理解Java并发编程至关重要,它为读者提供了理解后续内容的基石。 随着对线程知识的掌握,第二部分则引入了`ClassLoader`的概念。众所周知,Java的跨平台特性在很大程度上归功于其字节码和类加载...

    阿里专家级并发编程架构师课程-网盘链接提取码下载 .txt

    课程内容包括了JAVA手写线程池,UC线程池API详解,线程安全根因详解,锁与原子类,分布式锁原理与实现方式,并发编程-AQS等等针对性非常强的JAVA编程开发教程,这其中的内容对JAVA开发技能的拔尖,非常的有帮助。...

    java并发整理

    ### Java并发核心知识点详解 #### 一、Java并发基础概念 **并发简介**: Java并发是指在一个程序中同时运行多个线程的能力。每个Java程序至少包含一个线程,即启动时运行在`main`方法中的主线程。此外,在Java...

    JAVA并发编程实践

    #### 三、Java并发工具类详解 - **原子操作类**:`AtomicInteger`、`AtomicLong`等原子类提供线程安全的整型变量操作,无需显式同步。 - **并发集合**:`ConcurrentHashMap`、`CopyOnWriteArrayList`等集合类在高...

    Java2编程详解(Special_Edition_Using_Java)

    读者将学习到如何创建和管理线程,理解同步和互斥的概念,以及如何使用synchronized关键字和java.util.concurrent包来实现并发控制。 网络编程也是Java2的重要部分,书中会介绍Socket编程和HTTP协议,使开发者能够...

    JAVA高质量并发详解,多线程并发深入讲解

    #### 一、Java并发编程基础 - **基础知识:** - **线程基本概念:** Java线程是程序执行流的最小单元,一个线程包含一个程序计数器(PC)、虚拟机栈、本地方法栈、线程私有的工作内存。 - **线程生命周期:** 包括`...

    java 1.8 API离线手册

    - **java.util**:提供集合框架、泛型、日期时间、并发控制、随机数生成等功能。 - **java.net**:支持网络通信的类和接口。 - **java.sql**:处理数据库连接和SQL操作的API。 - **java.awt**和**javax.swing**...

Global site tag (gtag.js) - Google Analytics