同步锁(Lock)
通常认为:Lock提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock更灵活的结构,有很大的差别,并且可以支持多个Condition对象
Lock是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,
线程开始访问共享资源之前应先获得Lock对象。不过某些锁支持共享资源的并发访问,如:ReadWriteLock(读写锁),在线程安全控制中,
通常使用ReentrantLock(可重入锁)。使用该Lock对象可以显示加锁、释放锁。
class C {
//锁对象
private final ReentrantLock lock = new ReentrantLock();
......
//保证线程安全方法
public void method() {
//上锁
lock.lock();
try {
//保证线程安全操作代码
} catch() {
} finally {
lock.unlock();//释放锁
}
}
}
使用Lock对象进行同步时,锁定和释放锁时注意把释放锁放在finally中保证一定能够执行。
使用锁和使用同步很类似,只是使用Lock时显示的调用lock方法来同步。
而使用同步方法synchronized时系统会隐式使用当前对象作为同步监视器,
同样都是“加锁->访问->释放锁”的操作模式,都可以保证只能有一个线程操作资源。
同步方法和同步代码块使用与竞争资源相关的、隐式的同步监视器,并且强制要求加锁和释放锁要出现在一个块结构中,而且获得多个锁时,
它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的范围内释放所有资源。
Lock提供了同步方法和同步代码库没有的其他功能,包括用于非块结构的tryLock方法,已经试图获取可中断锁lockInterruptibly()方法,
还有获取超时失效锁的tryLock(long, timeUnit)方法。
ReentrantLock具有重入性,也就是说线程可以对它已经加锁的ReentrantLock再次加锁,
ReentrantLock对象会维持一个计数器来追踪lock方法的嵌套调用,
线程在每次调用lock()加锁后,必须显示的调用unlock()来释放锁,所以一段被保护的代码可以调用另一个被相同锁保护的方法。
JDK:
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。
锁是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问。一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁。不过,某些锁可能允许对共享资源并发访问,如 ReadWriteLock 的读取锁。
synchronized 方法或语句的使用提供了对与每个对象相关的隐式监视器锁的访问,但却强制所有锁获取和释放均要出现在一个块结构中:当获取了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的词法范围内释放所有锁。
虽然 synchronized 方法和语句的范围机制使得使用监视器锁编程方便了很多,而且还帮助避免了很多涉及到锁的常见编程错误,但有时也需要以更为灵活的方式使用锁。例如,某些遍历并发访问的数据结果的算法要求使用 "hand-over-hand" 或 "chain locking":获取节点 A 的锁,然后再获取节点 B 的锁,然后释放 A 并获取 C,然后释放 B 并获取 D,依此类推。Lock 接口的实现允许锁在不同的作用范围内获取和释放,并允许以任何顺序获取和释放多个锁,从而支持使用这种技术。
随着灵活性的增加,也带来了更多的责任。不使用块结构锁就失去了使用 synchronized 方法和语句时会出现的锁自动释放功能。在大多数情况下,应该使用以下语句:
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁。
Lock 实现提供了使用 synchronized 方法和语句所没有的其他功能,包括提供了一个非块结构的获取锁尝试 (tryLock())、一个获取可中断锁的尝试 (lockInterruptibly()) 和一个获取超时失效锁的尝试 (tryLock(long, TimeUnit))。
Lock 类还可以提供与隐式监视器锁完全不同的行为和语义,如保证排序、非重入用法或死锁检测。如果某个实现提供了这样特殊的语义,则该实现必须对这些语义加以记录。
注意,Lock 实例只是普通的对象,其本身可以在 synchronized 语句中作为目标使用。获取 Lock 实例的监视器锁与调用该实例的任何 lock() 方法没有特别的关系。为了避免混淆,建议除了在其自身的实现中之外,决不要以这种方式使用 Lock 实例。
除非另有说明,否则为任何参数传递 null 值都将导致抛出 NullPointerException。
内存同步
所有 Lock 实现都必须 实施与内置监视器锁提供的相同内存同步语义,如 The Java Language Specification, Third Edition (17.4 Memory Model) 中所描述的:
成功的 lock 操作与成功的 Lock 操作具有同样的内存同步效应。
成功的 unlock 操作与成功的 Unlock 操作具有同样的内存同步效应。
不成功的锁定与取消锁定操作以及重入锁定/取消锁定操作都不需要任何内存同步效果。
实现注意事项
三种形式的锁获取(可中断、不可中断和定时)在其性能特征、排序保证或其他实现质量上可能会有所不同。而且,对于给定的 Lock 类,可能没有中断正在进行的 锁获取的能力。因此,并不要求实现为所有三种形式的锁获取定义相同的保证或语义,也不要求其支持中断正在进行的锁获取。实现必需清楚地对每个锁定方法所提供的语义和保证进行记录。还必须遵守此接口中定义的中断语义,以便为锁获取中断提供支持:完全支持中断,或仅在进入方法时支持中断。
由于中断通常意味着取消,而通常又很少进行中断检查,因此,相对于普通方法返回而言,实现可能更喜欢响应某个中断。即使出现在另一个操作后的中断可能会释放线程锁时也是如此。实现应记录此行为。
读写锁:
public interface ReadWriteLock
ReadWriteLock 维护了一对相关的锁
,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁
可以由多个 reader 线程同时保持。写入锁
是独占的。
所有 ReadWriteLock 实现都必须保证writeLock 操作的内存同步效果也要保持与相关readLock 的联系。也就是说,成功获取读锁的线程会看到写入锁之前版本所做的所有更新。
与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程),读-写锁利用了这一点。从理论上讲,与互斥锁相比,使用读-写锁所允许的并发性增强将带来更大的性能提高。在实践中,只有在多处理器上并且只在访问模式适用于共享数据时,才能完全实现并发性增强。
与互斥锁相比,使用读-写锁能否提升性能则取决于读写操作期间读取数据相对于修改数据的频率,以及数据的争用——即在同一时间试图对该数据执行读取或写入操作的线程数。例如,某个最初用数据填充并且之后不经常对其进行修改的 collection,因为经常对其进行搜索(比如搜索某种目录),所以这样的 collection 是使用读-写锁的理想候选者。但是,如果数据更新变得频繁,数据在大部分时间都被独占锁,这时,就算存在并发性增强,也是微不足道的。更进一步地说,如果读取操作所用时间太短,则读-写锁实现(它本身就比互斥锁复杂)的开销将成为主要的执行成本,在许多读-写锁实现仍然通过一小段代码将所有线程序列化时更是如此。最终,只有通过分析和测量,才能确定应用程序是否适合使用读-写锁。
尽管读-写锁的基本操作是直截了当的,但实现仍然必须作出许多决策,这些决策可能会影响给定应用程序中读-写锁的效果。这些策略的例子包括:
- 在 writer 释放写入锁时,reader 和 writer 都处于等待状态,在这时要确定是授予读取锁还是授予写入锁。Writer 优先比较普遍,因为预期写入所需的时间较短并且不那么频繁。Reader 优先不太普遍,因为如果 reader 正如预期的那样频繁和持久,那么它将导致对于写入操作来说较长的时延。公平或者“按次序”实现也是有可能的。
- 在 reader 处于活动状态而 writer 处于等待状态时,确定是否向请求读取锁的 reader 授予读取锁。Reader 优先会无限期地延迟 writer,而 writer 优先会减少可能的并发。
- 确定是否重新进入锁:可以使用带有写入锁的线程重新获取它吗?可以在保持写入锁的同时获取读取锁吗?可以重新进入写入锁本身吗?
- 可以将写入锁在不允许其他 writer 干涉的情况下降级为读取锁吗?可以优先于其他等待的 reader 或 writer 将读取锁升级为写入锁吗?
当评估给定实现是否适合您的应用程序时,应该考虑所有这些情况。
读写锁模拟缓存机制
读写锁 实现缓存机制
修改整理后:
分享到:
相关推荐
Java中提供了`java.util.concurrent.locks.ReadWriteLock`接口来支持这种模式,但在本案例中,我们将模拟实现一个读写锁来理解其基本原理。 1. **读写锁接口定义**: - `Lock`接口:这是基础的锁接口,提供了获取...
Java多线程锁机制的应用场景非常广泛,例如,在数据库连接池、线程池、缓存系统等场景中,Java锁机制都扮演着至关重要的角色。 通过本文的讲解,我们可以更好地理解Java多线程锁机制的原理和应用场景,从而更好地...
3. Lock接口与ReentrantLock:提供更细粒度的锁控制,如公平锁、非公平锁、读写锁等。 4. volatile + CAS(Compare and Swap):无锁编程,适用于高并发场景。 四、线程池 线程池是一种线程复用技术,可以有效管理...
- **线程安全的数据结构**:为了确保并发访问的正确性,"SemQueue"的队列结构应该设计为线程安全的,这意味着所有对队列的操作都应考虑锁的使用,例如互斥锁(Mutex)或者读写锁(Read-Write Lock)。 2. **缓存...
综上所述,Java多线程下载图片涉及到的知识点包括线程的创建与管理、并发控制、异常处理、IO操作、线程同步、资源管理以及用户界面的更新等,这些都是构建高效、健壮的多线程应用所必需的技能。
Java多线程技术是Java编程中的重要组成部分,它允许程序同时执行多个任务,极大地提高了程序的效率和响应性。在本文中,我们将深入探讨多线程的概念、如何在Java中实现多线程以及相关的安全问题。 一、多线程概念 ...
下面将详细探讨读写锁的工作原理、优缺点以及如何在实际应用中使用。 1. **工作原理**: - **读锁**:当一个线程获取读锁时,其他线程也可以同时获取读锁,因为读操作通常是非破坏性的。这意味着多个线程可以并发...
在Java并发编程中,读写锁是用于优化多线程访问共享资源的一种机制,它可以提高对数据的并发访问效率。本文将深入探讨Java中的两种读写锁:ReentrantReadWriteLock和StampedLock,并分析它们的工作原理、特点以及...
在进行Java多线程编程时,要遵循良好的编程实践,如避免长时间持有锁、合理设置线程优先级、使用并发集合(如ConcurrentHashMap、CopyOnWriteArrayList等)以减少同步开销,以及及时关闭不再使用的线程池。...
3. Lock接口和ReentrantLock类:提供了比synchronized更细粒度的锁控制,如公平锁、非公平锁、读写锁等。 线程安全的集合类,如ConcurrentHashMap、CopyOnWriteArrayList等,设计用于多线程环境下的高效并发操作。 ...
在多线程环境下,Java的synchronized关键字和Lock接口可用于控制对缓存的并发访问。例如,ReentrantLock提供了比synchronized更细粒度的控制,支持公平性和非公平性锁。 10. **缓存的监控与调优** 为了优化缓存...
- Lock接口及其实现类如ReentrantLock:提供了更细粒度的锁控制,支持公平锁和非公平锁,以及可中断锁和读写锁。 - wait()、notify()和notifyAll()方法:在线程间进行通信,但必须在synchronized块中使用,否则会抛...
7. **线程同步机制**:Java提供了多种同步机制,如synchronized关键字、Lock接口(ReentrantLock、读写锁等)、wait/notify、Semaphore等,用于解决线程间的竞态条件和死锁问题。 三、Java并发工具类 8. **...
在设计高并发系统时,还需要考虑线程安全的设计模式,如生产者消费者模型、读写锁策略以及双检锁/双重校验锁(DCL)等。理解这些模式有助于构建可伸缩、健壮的系统。 最后,性能监控和调优也是高并发系统开发的重要...
- **锁的应用**:在读取或写入Excel数据时,如果涉及到共享的数据结构(如数据库连接、缓存),可能需要使用同步锁来保证操作的原子性,避免数据错乱。 4. **Excel处理库**: - **Apache POI**:这是一个广泛使用...
在这个学习笔记中,主要讨论了Java中的线程同步机制,包括volatile关键字、synchronized以及Lock接口,特别是ReentrantLock的使用。 首先,对于线程1和线程2的疑惑,调试(debug)模式并不能改变线程的执行顺序。...
- **锁机制**:包括内置锁(也称为监视器锁)和显式锁(如`java.util.concurrent.locks.Lock`接口)。 - **死锁**:多个线程互相等待对方释放资源,形成循环等待,导致所有线程都无法继续执行。 - **活锁**:线程...
Java多线程和并发知识是Java开发中的重要组成部分,它涉及到如何高效地利用系统资源,尤其是在多核CPU环境下,合理地使用多线程可以显著提升应用程序的性能。 **1. 理论基础** 1.1 为什么需要多线程 多线程的引入...