java.util.concurrent
API提供了一个称为Lock
的类,它可以序列化控制以便访问关键资源。 它提供了park()
和unpark()
等方法。
如果我们使用synchronized
关键字并使用wait()
和notify() notifyAll()
方法,我们也可以做类似的事情。
在实践中,哪种方式更好呢?为什么?
java.util.concurrent
API提供了一个称为Lock
的类,它可以序列化控制以便访问关键资源。 它提供了park()
和unpark()
等方法。
如果我们使用synchronized
关键字并使用wait()
和notify() notifyAll()
方法,我们也可以做类似的事情。
在实践中,哪种方式更好呢?为什么?
synchronized
。Lock.acquire();
doSomethingNifty(); // Throws a NPE!
Lock.release(); // Oh noes, we never release the lock!
您必须在每个地方明确使用 try{} finally{}
。
而对于 synchronized,它非常清晰,不可能出错:
synchronized(myObject) {
doSomethingNifty();
}
话虽如此,Lock
在更复杂的情况下可能更有用,这种情况下你不能像上面那样简单地获取和释放。如果符合您的需求,我会更倾向于避免首先使用裸的Lock
,而是选择更复杂的并发控制,例如CyclicBarrier
或LinkedBlockingQueue
。
我从未有过使用wait()
或notify()
的理由,但可能存在一些好的理由。
std::lock_guard
。 - WhiZTiMLock
和 Condition
(以及其他新的 concurrent
类)只是工具箱中的更多工具。我可以用我的旧钳子(即 synchronized
关键字)做我需要做的大多数事情,但在某些情况下它很难使用。当我在工具箱中添加了更多工具时,其中一些棘手的情况就变得更加简单:橡皮锤、球锤、撬棒和一些钉击。然而,我的旧钳子仍然有它的用途。synchronized
有助于保护代码中的错误,但这些同样的优点有时会在更复杂的情况下成为障碍。 这些更复杂的场景是并发包创建来帮助解决的。 但是,在代码中使用这些更高级别的结构需要更明确和仔细的管理。Lock
和 synchronized
之间的区别时做得很好(强调是我的):...
在不同范围内发生锁定和解锁时,必须注意采取措施以确保所有执行的代码在锁定被持有时都受到try-finally或try-catch的保护,以确保在必要时释放锁定。
锁定实现通过提供非阻塞尝试获取锁(tryLock())、可中断获取锁(lockInterruptibly())和可超时获取锁(tryLock(long, TimeUnit))的方式,提供了比使用同步方法和语句更多的功能。
...
synchronized
、volatile
或wait/notify)来完成。synchronized, volatile, wait, notify
。
Lock 类本身在工具箱的较低级别上,您甚至可能不需要直接使用它(大多数时候,您可以使用队列
和Semaphore等东西)。java.util.concurrent
比语言特性(例如synchronized
)更容易。当你使用 java.util.concurrent
时,你必须养成在编写代码之前完成 lock.lock(); try { ... } finally { lock.unlock() }
的习惯,而对于 synchronized
,从一开始就可以使用。仅基于这一点,我会说 synchronized
比 java.util.concurrent.locks.Lock
更容易(如果你想要它的行为)。 [第4段] (http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/Lock.html) - Iwan Aucamp为什么要使用synchronized
或java.util.concurrent.Lock
,存在4个主要因素。
注:当我提到内在锁定时,指的是同步锁定。
当Java 5发布ReentrantLocks时,它们证明比内在锁定有相当明显的吞吐量差异。如果您正在寻找更快的锁定机制并且正在运行1.5,请考虑使用j.u.c.ReentrantLock。 Java 6的内在锁定现在可比较。
j.u.c.Lock具有不同的锁定机制。可中断锁定-尝试锁定,直到锁定线程被中断;定时锁定-尝试锁定一定时间并在失败时放弃;tryLock-尝试锁定,如果其他线程持有锁则放弃。这些都是除简单锁定之外的内容。内在锁定只提供简单锁定。
主要的区别在于公平性,也就是请求是否按照FIFO(先进先出)处理或者是否可以插队?方法级别的同步可以确保公平或FIFO方式分配锁。使用
synchronized(foo) {
}
或者
lock.acquire(); .....lock.release();
没有锁定并不保证公平。
如果有很多线程争用锁,你很容易遇到抢占问题,其中新请求获得锁定,而旧请求被卡住。我见过有200个线程短时间内请求锁,第二个到达的线程最后被处理。这对于某些应用程序来说没问题,但对于其他一些应用程序来说会有致命的影响。
有关此主题的全面讨论,请参阅Brian Goetz的《Java并发实战》书籍第13.3节。
使用同步方法或语句提供对每个对象关联的隐式监视器锁的访问,但强制所有锁获取和释放以块结构方式发生。
锁实现通过提供非阻塞尝试获取锁(tryLock()
)、可以中断的获取锁尝试(lockInterruptibly()
)和可以超时的获取锁尝试(tryLock(long, TimeUnit)
)提供了比使用同步方法和语句更多的功能。
锁类还可以提供与隐式监视器锁完全不同的行为和语义,例如保证顺序、非可重入使用或死锁检测。
ReentrantLock:简单来说,ReentrantLock
允许对象从一个临界区重新进入到另一个临界区。由于您已经拥有进入一个临界区所需的锁,因此可以使用当前锁在同一对象上进入其他临界区。
ReentrantLock
的主要特点如下 article
您可以使用 ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock
进一步控制读写操作的细粒度锁定。
除了这三个 ReentrantLock,Java 8 还提供了另一个 Lock
StampedLock:
Java 8 提供了一种新类型的锁,称为 StampedLock,它也支持读写锁,就像上面的示例一样。与 ReadWriteLock 不同,StampedLock 的锁定方法返回一个由长整型值表示的标记。
您可以使用这些标记来释放锁或检查锁是否仍然有效。此外,标记锁还支持另一种锁模式,称为乐观锁定。
请看这篇关于不同类型的ReentrantLock
和StampedLock
锁使用的文章。
锁和synchronized的主要区别:
锁让程序员的工作更加轻松。下面是一些可以通过锁轻松实现的情况。
然而,锁和条件都建立在synchronized机制上。因此,使用锁肯定可以实现您可以使用锁实现的相同功能。但是,使用synchronized解决复杂场景可能会让你的工作变得困难,并且可能会偏离解决实际问题的目标。
自动释放:无需使用try-finally,防止遗忘释放。
synchronized {
someCode();
}
对比
lock.lock();
try {
someCode();
} finally {
lock.release();
}
更少的额外行数(如上所示的例子)
能够标记整个方法为synchronized
,避免额外的行数和额外的缩进
public synchronized someMethod() {
someCode();
}
锁的优势:
.lockInterruptibly()
, .tryLock()
, tryLock(timeout)
Condition
。例如,ArrayBlockingQueue
同时持有notEmpty
和notFull
条件。sync.exclusiveOwnerThread
。获取锁状态的API: .getHoldCount()
, .hasQueuedThreads()
等。Map<ResourceObject, Lock>
new ReentrantLock(true)
ReadWriteLock
允许并行读取