接上一篇:
http://caoyaojun1988-163-com.iteye.com/blog/1290759
4、运用:
AbstractQueuedSynchronizer类将上述功能联系在一起,作为一个“模板方法模式[6]”中的模板类,作为其他同步器的基类。子类只是实现预定义方法,实现通过获取锁和释放锁的操作来检查和更新状态。然而,AbstractQueuedSynchronizer的子类本身不可用于ADTS,因为这些类暴露的用于内部控制获取和释放的策略的方法,应该对用户不可见。所有java.util.concurrent包中的同步类声明了一个私有的内部AbstractQueuedSynchronizer子类用于委托它的所有同步方法。这也使得public方法可以根据不太的同步器给予适当的名称。
例如,假设最小的Mutex类,当同步状态为0意味着解锁,为1意味着锁定。这个类的同步方法不需要参数值,因此直接默认使用零。
class Mutex { class Sync extends AbstractQueuedSynchronizer { public boolean tryAcquire(int ignore) { return compareAndSetState(0, 1); } public boolean tryRelease(int ignore) { setState(0); return true; } } private final Sync sync = new Sync(); public void lock() { sync.acquire(0); } public void unlock() { sync.release(0); } }
此示例的更全面的版本,以及与其他使用文档,可以在J2SE文档中找到,当然还有许多变种。例如,tryAcquire可以使用“test-and-test-and-set“改变值之前检查状态值。
这可能是令人惊讶的,使用委托(delegation )和虚方法(virtual methods),来构造性能敏感的互斥锁, 然而,各种面向对象的设计,动态编译器早已非常成熟;当同步器频繁调用的是。他们都可以很好的优化掉这个开销;AbstractQueuedSynchronizer类还提供了一些方法,可以设置同步类的控制策略。例如,基于acquire方法的超时和中断的版本、独占模式的同步器、锁。AbstractQueuedSynchronizer类也提供了一系列的方法(如acquireShared),他们与tryAcquireShared和tryReleaseShared方法不同,可以通知框架(通过其返回值)将来的acquires方法是否可以成功;最终可以实现级联信号唤醒多个线程。
尽管序列化(永久存储或传输)同步器通常是不明智的,但是由于这些类通常用于构建其他类,如线程安全的集合。所以通常实现序列化。AbstractQueuedSynchronizer和ConditionObject类提供的方法来序列化同步状态,而不是被阻塞线程的底层或其他本质上临时状态。即便如此,大多数的同步类反序列化时只是复位同步状态作为初始值,与内置锁反序列化时总是设置为解锁状态的隐含的政策相同。虽然不是必须的,但仍明确支持final域的反序列化。
4.1 公平性
虽然他们是基于FIFO队列,同步器不一定是公平的。可以注意到,在基本的acquire算法中(3.3节),tryAcquire是再排队前进行检查。因此,新请求获取锁(acquiring)的线程可以优先于队列的头部第一个节点对应的线程。
这虽然破坏了FIFO的策略,但是也有普遍高于其他技术的总吞吐量。这减少了有锁可以用,但是因为预定的下一个线程还在唤醒(unblocking)的过程中,所以还没有线程获取到锁的时间,同时,它通过只允许一个(队列的第一个)线程唤醒,避免了过度的、无用的、竞争;可以实现自己的tryAcquire方法,在交回控制权前简单的多试几次,这样可以加剧不公平性, 如果有需要,开发者可以自己创建一个自己的简单持有的同步器;
FIFO同步器是相对最公平的锁;即便它会被打破,一个等待唤醒的(unpark)的线程与所有打破规则进入的线程都有一个公平的竞争机会,如果失败它会重新阻塞;当然,如果闯入的线程比获取一个等待唤醒的线程唤醒到达的快,队列中第一个线程几乎没有赢的概率,所以几乎总是reblock, 简单持有的同步器,通常用于多个闯入线程和多个等待唤醒的线程在多处理器的场景,此时队列中的第一个线程被唤醒;如下图所示,既要维持一个或多个线程时处理器的利用率,同时也要避免饥饿。
如果需要更加公平的策略,也相对比较简单。程序员可以自己定义 tryAcquire,如果不是队列的head节点就失败(返回false),达到严格的公平;可以通过少数提供的检查方法之一getFirstQueuedThread,来检查是否是第一个节点。
一个更快,更严格的变种是如果队列(暂时)是空,也让tryAcquire成功,在这种情况下,如果多个线程遇到一个空队列的情况下竞争,其中至少有一个及第一个得到执行权的线程不需要入队。这一策略使得在所有java.util.concurrent中的同步器,支持“公平”的模式。
虽然在实践中往往希望公平,但是即便设置公平也不会得到保证,因为Java语言规范并不提供调度保证。例如,即使有一个严格意义上的公平同步,如果他们从来不需要阻止等待对方,JVM可以纯粹按照顺序选择运行线程,而实际上,在单处理器的环境中,这些线程在被上下文切换之前可以每次运行一个时间片。如果有一个线程持有一个互斥锁,它必须暂时先得到时间片才能释放锁,不然就阻塞那些需要锁的线程,这导致延长了同步器是可用的,但是不能被线程获取到的时间;使用公平的同步器往往在多处理器有很好的表现,因为这样有更好的并发,因此出现竞争的几率更多。
即使他们在高竞争的场景下,性能不是很理想,但是公平锁任然可以工作的很好,同时也保持编码的简洁。例如:当维护相对较长的代码或者延长锁间的时间间隔,在这种情况下,可以提示一定的性能,但是饥饿的风险更大。同步框架将最终的决定权交由用户。
4.2 同步器
这里描述一个草图关于怎样使用这个框架定义java.util.concurrent中的同步器;ReentrantLock类使用同步状态(递归)维护锁的数量。在获取锁的时候,它会记录当前线程的ID,并且递归检查是否有异常线程获取锁导致的非法的状态异常。该类也提供ConditionObject,暴露它的监测和检查方法。同时支持“公平”的模式的选项,在内部通过实现两个不同AbstractQueuedSynchronizer的子类(公平的实现不允许打破规则),并为每个ReentrantLock的实例选择以个合适的实现。
ReentrantReadWriteLock类使用16位的同步状态持有写锁定计数,其余16位持有读锁计数。WriteLock使用与ReentrantLock同样的结构。ReadLock使用acquireShared的方法,以允许多个读者。
Semaphore类(一个计数信号)使用同步状态保持当前计数。它定义acquireShared方法递减计数,如果为负数了就阻塞当前线程;同样使用tryRelease递增计数,如果是正数的时候,就唤醒线程。
CountDownLatch类使用同步状态代表计数。当减到零的时候所有所有获取锁的操作全部成功返回。
FutureTask类使用同步状态来代表future 的运行状态(初始,运行,注销,完成)。调用释放(release)来设置或取消一个future ,等待线程处理完成,acquire返回唤醒线程。
SynchronousQueue类(一个CSP风格的切换)使用内部等待节点调整生产者和消费者;它采用了同步状态来作为标识,当消费者改变状态的时候允许生产者继续处理,反之亦然。
java.util.concurrent包的用户当然可以为自己的应用程序定义自己的同步器;例如,在那些经常遇到,但是包里没有提供的各种Win32事件的语义类,二进制闩锁,锁集中管理,并基于树的壁垒。
原文见第一篇的附件
下一篇:http://caoyaojun1988-163-com.iteye.com/blog/1315089
本站支持 pay for your wishes
相关推荐
在 java.util.concurrent 多线程框架中,还提供了多种其他机制,包括并发集合、同步器、lock 等,以便开发者更方便地编写高效、可靠的多线程程序。并发集合提供了多种机制,包括 CopyOnWriteArrayList、...
Java.util.concurrent是Java 5.0引入的一个重要包,它为多线程编程提供了一组高级并发工具。这个包的设计者是Doug Lea,它的出现是JSR-166的一部分,也被称作Tiger更新。Java.util.concurrent的引入是为了解决传统...
`java.util.concurrent`包中的`AbstractQueuedSynchronizer`框架为Java开发者提供了一个强大的工具箱,使得他们能够高效地实现各种同步器。通过对同步状态的有效管理、阻塞和唤醒机制的优化以及灵活的扩展性设计,...
文档标题“java.util.concurrent同步器框架”和描述“Doug Lea的java.util.concurrent同步器框架”表明本文将探讨由Doug Lea所撰写的关于Java并发编程中同步器框架的内容。文档中提到了AbstractQueuedSynchronizer类...
AQS(AbstractQueuedSynchronizer)是Java.util.concurrent包中同步器的基础框架,它的核心设计思想与实现方法在Doug Lea先生的这篇论文中有详细的介绍。论文详细阐述了AQS框架的原理、设计、实现、应用以及性能等...
在Java并发编程中,`java.util.concurrent`(简称JUC)提供了丰富的类和接口,如Executor框架、线程池、并发集合、同步工具类等。这些工具使得程序员能够更方便地管理线程,避免了传统的锁和同步机制带来的复杂性和...
Java.util.concurrent(JUC)是Java平台中的一个核心包,专门用于处理多线程并发问题。这个包包含了大量的工具类和接口,极大地简化了并发编程的复杂性,提高了程序的性能和可伸缩性。本测试源文件主要是针对JUC并发...
总之,`java.util.concurrent` 提供的工具使得并发编程变得更加容易和高效,是 Java 并发编程的基石,无论是对于初学者还是经验丰富的开发者,理解和掌握这个包都是非常重要的。通过熟练运用这些工具,开发者可以...
然而,现代的多线程编程通常更倾向于使用并发集合,如`java.util.concurrent.CopyOnWriteArrayList`,它在读多写少的场景下有更好的性能。 6. **编程高手箴言** - 虽然`Vector`提供了线程安全,但其性能可能不满足...
在实际项目中,结合`java.util.concurrent`包与其他工具,比如Spring框架的ThreadPoolTaskExecutor,可以构建出高效、可扩展的并发解决方案。同时,持续学习和实践是提升并发编程能力的关键,不断探索新的并发模式和...
23. **`java.util.concurrent.locks.Lock`** 和 **`java.util.concurrent.locks.ReentrantLock`**: 锁机制,用于线程同步。 24. **`java.util.ArrayList`**: 用于创建堆栈、队列和双端队列的实现,如`ArrayDeque`。...
8. **Fork/Join框架**: `java.util.concurrent.ForkJoinPool` 和 `RecursiveTask` 或 `RecursiveAction` 用于并行执行可拆分任务,适合大量计算工作。 9. **ScheduledExecutorService**: 提供定时及周期性任务的...
`java.util.Queue`接口及其实现如ArrayDeque、LinkedList(作为Queue的实现),以及`java.util.concurrent`包下的并发队列如LinkedBlockingQueue、ConcurrentLinkedQueue等,提供高效的数据同步和处理机制。...
总结一下,Java中实现定时任务主要依靠`java.util.Timer`、`java.util.concurrent.ScheduledExecutorService`等原生API,以及一些第三方库如Quartz。在实际开发中,选择合适的定时任务解决方案需要考虑项目的复杂性...
`Thread`类允许创建和管理独立执行的线程,而`java.util.concurrent`包包含了许多高级并发工具,如线程池、未来结果(Future)和同步器(Semaphore)。 4. **网络编程**:`java.net`包提供了网络通信的接口和类,如...
- `java.util.concurrent` 包:包含线程池、并发容器、同步工具类,如`ExecutorService`、`CountDownLatch`、`CyclicBarrier`等。 7. **反射工具类**: - `java.lang.reflect` 包:提供反射API,可以在运行时动态...
3. **同步容器**: `java.util.concurrent`包中的`BlockingQueue`接口及其实现(如`ArrayBlockingQueue`, `LinkedBlockingQueue`)是线程安全的数据结构,适用于生产者-消费者模型。此外,还有`ConcurrentHashMap`,...
java.util.concurrent.locks 为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器。 java.util.jar 提供读写 JAR (Java ARchive) 文件格式的类,该格式基于具有可选清单文件的标准 ZIP 文件格式。 ...