This article is about locks in Java. Here you can read about their usage, fairness policies and performance.
Locks are available since Java 1.5.
这是文章关于所得应用,公平性,性能的文章.
从Java5开始锁开始应用.
Interfaces
First of all, lets see at interfaces.
java.util.concurrent.Lockjavadoc
1 2 3 4 5 6 7 8 |
public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition(); } |
As we see, it prodives opportunity of acquiring lock by different ways:
从接口可见提供不同的方式来获得锁.
· if we are using lock() and the lock is not available the current thread will be suspended until the lock will be released;
· 如果调用lock(),并且锁对于当前线程不可用,当前线程阻塞知道锁被释放.
· lockInterruptible() method acquires the lock until the current thread will be interrupted or lock will be released;
· 如果调用lockInterruptible,当前线程阻塞知道被中断或者锁被释放.
· tryLock() method acquires the lock only if it is available at the time of invocation (non-blocking, not waiting for the lock will be released);
· tryLock方法如果不能获得锁,则不会被阻塞,不用等待锁释放.
· if we want to acquire the lock interruptibly with the specified waiting timeout we should use tryLock(...) method.
· tryLock也支持传入等待时间.如果在等待时间范围内拿不到锁,则不再继续等待.
There is only one method for unlocking the lock: unlock() and it works as it named.
释放锁通过unlock方法.
Locks are more flexible and configurable alternative for synchronized methods and statements, but we should remeber that instead of synchronized, noone will unlock the lock for us if an exception occures or return statement invokes. So, in most cases, the following idiom should be used:
锁更灵活可控制的,是synchronized内置锁的方法和语句块的代替方案,但是要记住,这种方式也带来了复杂性, 如果忘记释放锁, 程序就会被阻塞.
所以应该下面写法.
1 2 3 4 5 6 7 |
Lock l = ...; l.lock(); try { // some business logic that needs to be synchronized } finally { l.unlock(); } |
java.util.concurrent.locks.ReadWriteLockjavadoc
1 2 3 4 |
public interface ReadWriteLock { Lock readLock(); Lock writeLock(); } |
ReadWriteLock is a pair of assotiated locks. First lock is used only for read-only operations (supports multiple locks at the same time) and the second lock is for write operations (exclusivelock). The read lock can be acquired only if the write lock is released. When we lock the write lock, it will wait for releasing of all read locks and then acquires. ReadWriteLock will improve performance in case of using multiple readers and much smaller number of writers, because many of readers will work at the same time.
ReaderWritherLock提供了一堆相关的锁, readLock提供了读锁,支持多个读锁.writeLock提供排他的写锁. 读锁只有在写锁释放的情况下才能获得(同一线程在有写锁的情况下可以获得读锁).如果我们尝试获得写锁,则需要等待所有的读锁释放(读锁升级). ReadWriteLock在多读少写的场景下,可以提高性能.
Implementations
Java provides two implementations of these locks that we care about - ReentrantLock and ReentrantReadWriteLock. As you see, both of them are reentrant. It means that a thread can acquire the same lock multiple times without any issue. In fact reentrant locking increments special thread-personal counter (unlocking - decrements) and the lock will be released only when counter reaches zero.
Each of lock’s implementations has additional methods that help us. Here are some of them:
在本文中关心的锁里,在JAVA提供了ReentrantLock/ReentrantReadWriteLock(看了下源代码后者实现了ReadWriteLock), 都是可重入的, 意味着一个线程可以获得相同的lock多次.实际上可重入锁添加线程的counter(解锁是减少counter),当counter为0时, 锁释放.
· for ReentrantLock class:
o isHeldByCurrentThread() returns true iff the lock is locked by the current thread;
o 如果锁被当前线程所有,返回true.
o getHoldCount() returns number (int) of locks on the lock by the current thread;
o 放回当前锁当前线程在锁上lock的次数.
o getQueueLength() returns an estimate number of threads that waits to acquire the lock;
o 返回大约有多少线程等待获取锁.
o isLocked() returns true if any thread holds this lock and false otherwise;
o 返回true如果锁别任何线程所有.否则返回false.
· for ReentrantReadWriteLock class:
o isWriteLocked() returns true if someone holds the write lock;
o 返回true如果写锁被线程所有.
o isWriteLockedByCurrentThread() returns true if the lock is locked by the current thread;
o 返回true如果写锁被当前线程所有.
o getReadHoldCount() returns number (int) of locks on the read lock by the current thread.
o 返回当前线程在读锁上锁定多少次.
Fairness
One of the interesting features of reentrant locks is fairness. When we are creating an instance of ReentrantLock or ReentrantReadWriteLock we can pass fair flag to the constructor. The difference of fair and non-fair locks is in granting access policy to threads that wait in the queue to lock the lock. The fair lock grants access to the longest-waiting thread, so it look like FIFO (First-In-First-Out). A non-fair lock does not guarantee any particular access order. Performance of fair locks is near to synchronized block and non-fair locks is much faster than them.可重入锁的一个特点是公平性策略.如果是公平锁则类似于FIFO,公平锁的性能接近synchronized内置锁,反之不保证访问顺序,非公平锁则快的多.
By default, all locks are non-fair. Synchronized keyword and statement are non-fair, too. Fair locks have some disadvantages:Default所有的锁是非公平的,synchronized语句也是非公平的. 公平锁的缺点:
· performance degradation;
· 性能退化.
· fair locks do not guarantiee fair thread ordering in real world.
· 公平锁并不能保证线程的执行顺序.
Micro-benchmarking in Java is not so good as we want. There are several things that can change results of tests:
有几点在我们测试时会改变我们的测试结果,这在java的基准测试中并能做到我们期望的理想.
· JVM warmup (the code becomes faster and faster while its working);
· JVM预热.
· Class loading (all application classes must be loaded);
· 类加载
· JIT compilation (JVM needs time to find hot parts of the code);
· 运行时编译.
· GC (gc can happen while benchmarking and increase the time much).
· 垃圾收集.
Instead of this, I think that we can do some micro-benchmarks to check fair locks performance. We have T threads, each of them will lock the lock, increment global counter, increment personal counter and unlock the lock. Test will be ended when the global counter reaches N. So, all threads should have equal values of personal counters (small deviation can be ignored) with fair lock and different values with non-fair lock. Lets see at average results of running such micro-benchmark. I ran them for 50 times with N=1000000 and T=10 on my laptop (i3 330m 2.13GHz, 8Gb RAM, JDK 1.7.0) and aggregated results. ‘Deviation’ - this is how personal counter differs from the expected value (N/T).
Fair Lock
1 2 3 4 5 6 |
Deviation: max: 0,3222% avg: 0,0644% min: 0,0351%
Average takes: 10304ms |
Non-fair Lock
1 2 3 4 5 6 |
Deviation: max: 20,1312% avg: 2,1682% min: 0,6022%
Average takes: 104ms |
You can find sources of the test here. The results of test match javadoc’s warning about fair locks absolutly:
1 2 3 4 5 |
* Programs using fair locks accessed by many threads * may display lower overall throughput (i.e., are slower; often much * slower) than those using the default setting, but have smaller * variances in times to obtain locks and guarantee lack of * starvation. |
As we see “much slower” is up to 100 times slower!
So, we should remember about all features of locks and their configurations. I think that we can use ReentrantLock and ReentrantReadWriteLock without any indeterminacy now.