本文共 11402 字,大约阅读时间需要 38 分钟。
接口
抽象类
两个接口
condition 方法
void | () Causes the current thread to wait until it is signalled or . |
boolean | (long time, unit) Causes the current thread to wait until it is signalled or interrupted, or the specified waiting time elapses. |
long | (long nanosTimeout) Causes the current thread to wait until it is signalled or interrupted, or the specified waiting time elapses. |
void | () Causes the current thread to wait until it is signalled. |
boolean | ( deadline) Causes the current thread to wait until it is signalled or interrupted, or the specified deadline elapses. |
void | () Wakes up one waiting thread. |
void | () Wakes up all waiting threads. |
lock方法
void | () Acquires the lock. |
void | () Acquires the lock unless the current thread is . |
| () Returns a new instance that is bound to this |
boolean | () Acquires the lock only if it is free at the time of invocation. |
boolean | (long time, unit) Acquires the lock if it is free within the given waiting time and the current thread has not been . |
void | () Releases the lock.
|
| () 返回用于阅读的锁。 |
| () 返回用于写入的锁。 |
以上是它们内部定义的方法。Lock中有一个newCondition方法,所以Condition都是从Lock中创建出来的。
Returns a new instance that is bound to this Lock
instance.
Before waiting on the condition the lock must be held by the current thread. A call to will atomically release the lock before waiting and re-acquire the lock before the wait returns.
Lock 接口支持那些语义不同(重入、公平等)的锁规则,可以在非阻塞式结构的上下文(包括 hand-over-hand 和锁重排算法)中使用这些规则。主要的实现是 ReentrantLock。
ReadWriteLock 接口以类似方式定义了一些读取者可以共享的锁, 但是把写者排除在外。此包只提供了一个实现,即 ReentrantReadWriteLock,因为它适用于大部分的标准用法上下文。但程序员可以创建自己的实现方法来覆盖非标准要求。
Condition 接口描述与锁有关联的条件变量。这些变量在用法上与 Object.wait 访问的隐式监视器用法类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同。
以读写锁为例来看原理:
public class ReentrantLock implements Lock, java.io.Serializable { ... public Condition newCondition() { return sync.newCondition(); }}public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable { ... public static class ReadLock implements Lock, java.io.Serializable { ... public Condition newCondition() { throw new UnsupportedOperationException(); //读锁不支持Condition } } ... public static class WriteLock implements Lock, java.io.Serializable { ... public Condition newCondition() { return sync.newCondition(); } }}
读写锁中的读锁不支持Condition,而写锁和互斥锁支持Condition。
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { public class ConditionObject implements Condition, java.io.Serializable { ... private transient Node firstWaiter;// 阻塞队列队头 private transient Node lastWaiter; // 阻塞队列队尾 ... }}
Sync继承自AQS,AQS有ConditionObject类,ConditionObject内定义了阻塞队列。
Sync类内定义了newCondition()方法得到ConditionObject,而写锁和互斥锁都调用了sync的newCondition()。
final ConditionObject newCondition() { return new ConditionObject();}
await()实现
public final void await() throws InterruptedException { if (Thread.interrupted()) //在执行await()时,收到中断信号,抛出异常 throw new InterruptedException(); Node node = addConditionWaiter(); //往阻塞队列加入线程 int savedState = fullyRelease(node);//在加入队列前先要先释放锁 int interruptMode = 0; while (!isOnSyncQueue(node)) { LockSupport.park(this); //阻塞自己 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } if (acquireQueued(node, savedState) && interruptMode != THROW_IE) //线程被唤醒后,再次获取锁 interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); // 被中断唤醒时,向外抛出异常 }//addConditionWaiter()的具体实现,线程在调用await()时,已经先拿到锁了,//所以,在往队列添加线程时,不用CAS操作private Node addConditionWaiter() { Node t = lastWaiter; if (t != null && t.waitStatus != Node.CONDITION) { unlinkCancelledWaiters(); t = lastWaiter; } Node node = new Node(Thread.currentThread(), Node.CONDITION); if (t == null) firstWaiter = node; else t.nextWaiter = node; lastWaiter = node; return node;}//checkInterruptWhileWaiting(Node node)的实现private int checkInterruptWhileWaiting(Node node) { return Thread.interrupted() ? (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0;}final boolean transferAfterCancelledWait(Node node) { if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) { //尝试将线程状态从条件状态设置为同步状态0,成功后将节点加入AQS同步队列尾部 enq(node); return true; } while (!isOnSyncQueue(node)) // 设置失败后,自旋尝试让当前线程的CPU执行时间让出,直到唤醒的节点在AQS同步队列为止 Thread.yield(); return false;}
线程被唤醒有两种可能,别的线程调用unpark()或者中断唤醒。所以在被重新唤醒后要判断是否是中断唤醒,如果是,则跳出循环,然后抛出异常。而如果是被signal()唤醒的话,线程会被放到同步队列中,同样会跳出循环。
awaitUninterruptibly()的实现
public final void awaitUninterruptibly() { Node node = addConditionWaiter(); int savedState = fullyRelease(node); boolean interrupted = false; while (!isOnSyncQueue(node)) { LockSupport.park(this); if (Thread.interrupted()) interrupted = true; } if (acquireQueued(node, savedState) || interrupted) selfInterrupt();}
与await()的区别在于,收到中断后不会抛出异常,也就是不响应异常。
signal()的实现
public final void signal() { if (!isHeldExclusively()) // 判断当前线程是否持有锁 throw new IllegalMonitorStateException(); Node first = firstWaiter; //获取第一个等待线程 if (first != null) doSignal(first);}private void doSignal(Node first) { //唤醒第一个等待线程 do { if ( (firstWaiter = first.nextWaiter) == null) lastWaiter = null; first.nextWaiter = null; } while (!transferForSignal(first) && (first = firstWaiter) != null);}final boolean transferForSignal(Node node) { if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) //修改线程状态,从条件等待修改为运行状态0 return false; Node p = enq(node); //将节点放进同步队列里 int ws = p.waitStatus; if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) LockSupport.unpark(node.thread); //唤醒线程 return true;}
Condition与Lock的关系是Condition是Lock的一部分,可以理解为一个锁Lock对应若干个条件Condition,调用await()时会将当前的线程加入到条件队列中,signal()则是将条件队列中的线程加入到同步队列中。
调用await()的作用相当于sychronized里调用wait(),signal()则相当于notify()。当使用synchronized修饰一个代码块时,是通过监控当前当代码块所在类的实例对象。当一个线程获取到对应的锁,执行该代码块时,其他线程只能处于等待状态,等待当前线程释放锁,在这里释放锁的情况有两种:
第一种:当前线程执行完毕,释放该锁
第二种:当前线程执行异常,JVM让线程自动释放锁
如果当前线程因为一些IO操作或者一些其他原因(比如sleep很久),那么,其他线程一直处于等待,无法获得该锁,无法进入代码块。这会大大的影响程序的效率,程序的执行。这就需要控制当前线程执行,不会让其他线程一直等待下去,Lock就能起到控制的作用。
Lock可以实现,线程有没有获取到锁,这个synchronized是无法做到的。
两点一定要记住:第一点 , synchronized是java内置的特性,但是Lock的功能更强大。 第二点:Lock锁的释放,是需要手动释放的,但是synchronized是自动释放的。synchronized和object对象的wait/notify/notifyall一起组合使用,效果是最好的。2.2 lock和condition关系
Condition可以替代传统的线程间通信,用await()替代wait,用signal替代notify(),用signalAll()替代notifyAll()。因为Object下面的wait/notify/notifyAll方法都是final的,所以名称上全都发生了改变。传统线程通信方式,condition都能实现。注意:condition()是被绑定到Lock上面的,要创建一个Lock的conditon,需要用newCondition 。现在知道了,synchronized和notidy/wait/notifyAll结合使用, lock和condition的await/signal/signalAll结合使用。
condition的强大之处,它可以为多个线程之间创建不同的condition。
java.util.concurrent.ArrayBlockingQueue的部分功能,实现put()和take()。假定有一个绑定的缓冲区,它支持put 和take 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存put 线程和take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个condition 实例来做到这一点。
代码如下:class BoundedBuffer { final Lock lock = new ReentrantLock(); //锁对象 final Condition notFull = lock.newCondition(); //写线程锁 final Condition notEmpty = lock.newCondition(); //读线程锁 final Object[] items = new Object[100];//缓存队列 int putptr; //写索引 int takeptr; //读索引 int count; //队列中数据数目 //写 public void put(Object x) throws InterruptedException { lock.lock(); //锁定 try { // 如果队列满,则阻塞 <写线程> while (count == items.length) { notFull.await(); } // 写入队列,并更新写索引 items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; // 唤醒 <读线程> notEmpty.signal(); } finally { lock.unlock();//解除锁定 } } //读 public Object take() throws InterruptedException { lock.lock(); //锁定 try { // 如果队列空,则阻塞 <读线程> while (count == 0) { notEmpty.await(); } //读取队列,并更新读索引 Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; // 唤醒 <写线程> notFull.signal(); return x; } finally { lock.unlock();//解除锁定 } } 写线程> 读线程> 读线程> 写线程>
class Outputter1 { private Lock lock = new ReentrantLock();// 锁对象 public void output(String name) { lock.lock(); // 得到锁 try { for(int i = 0; i < name.length(); i++) { System.out.print(name.charAt(i)); } } finally { lock.unlock();// 释放锁 } } }
区别:
需要注意的是,用sychronized修饰的方法或者语句块在代码执行完之后锁自动释放,而是用Lock需手动释放,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在try内,释放锁放在finally内!
读取线程和写入线程互不干扰是正常的,但是 两个读线程之间不需要互斥。
我们可以用读写锁ReadWriteLock实现:
class Data { private int data;// 共享数据 private ReadWriteLock rwl = new ReentrantReadWriteLock(); public void set(int data) { rwl.writeLock().lock();// 取到写锁 try { System.out.println(Thread.currentThread().getName() + "准备写入数据"); try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } this.data = data; System.out.println(Thread.currentThread().getName() + "写入" + this.data); } finally { rwl.writeLock().unlock();// 释放写锁 } } public void get() { rwl.readLock().lock();// 取到读锁 try { System.out.println(Thread.currentThread().getName() + "准备读取数据"); try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "读取" + this.data); } finally { rwl.readLock().unlock();// 释放读锁 } } }
与互斥锁定相比,读-写锁定允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程)
从理论上讲,与互斥锁定相比,使用读-写锁定所允许的并发性增强将带来更大的性能提高。在实践中,只有在多处理器上并且只在访问模式适用于共享数据时,才能完全实现并发性增强。——例如,某个最初用数据填充并且之后不经常对其进行修改的 collection,因为经常对其进行搜索(比如搜索某种目录),所以这样的 collection 是使用读-写锁定的理想候选者。
转载地址:http://uexzb.baihongyu.com/