The Lock interface supports three forms of lock acquisition interruptible, non-interruptible, and timed.
We can acquire locks between threads. A thread will wait until the lock is released from the thread holding the lock:
@Test
void lock() throws InterruptedException {
Thread withDelayedLock = new Thread(() -> {
lock.lock();
log.info("Acquired delayed");
lock.unlock();
});
lock.lock();
withDelayedLock.start();
Thread.sleep(500);
log.info("Will release");
lock.unlock();
withDelayedLock.join();
}
Since the threads blocks on the lock we might as well interrupt the thread. In this case we can lock lockInterruptibly, and apply any logic in case of an Interrupt:
@Test
void lockInterruptibly() throws InterruptedException {
Thread withDelayedLock = new Thread(() -> {
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
log.error("interrupted while waiting",e);
}
});
lock.lock();
withDelayedLock.start();
withDelayedLock.interrupt();
lock.unlock();
withDelayedLock.join();
}
A thread can also try to acquire a lock which is already acquired, and exit immediately instead of blocking.
@Test
void tryLock() throws InterruptedException {
Thread withDelayedLock = new Thread(() -> {
boolean locked = lock.tryLock();
assertFalse(locked);
});
lock.lock();
withDelayedLock.start();
Thread.sleep(500);
lock.unlock();
withDelayedLock.join();
}
Also a time period can be specified until the lock is acquired.
@Test
void tryLockTime() throws InterruptedException {
Thread withDelayedLock = new Thread(() -> {
try {
boolean locked = lock.tryLock(100, TimeUnit.MILLISECONDS);
assertFalse(locked);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
lock.lock();
withDelayedLock.start();
Thread.sleep(500);
lock.unlock();
withDelayedLock.join();
}
Another thing of importance with locks is memory.
From the documentation
All {@code Lock} implementations <em>must</em> enforce the same
* memory synchronization semantics as provided by the built-in monitor
* lock, as described in
* Chapter 17 of
* <cite>The Java Language Specification</cite>:
*
<ul>*
<li>A successful {@code lock} operation has the same memory
* synchronization effects as a successful <em>Lock</em> action.
*</li>
<li>A successful {@code unlock} operation has the same
* memory synchronization effects as a successful <em>Unlock</em> action.
*</li>
</ul>
In order for the results to be flushed on the main memory we need to use lock and unlock.
Take the following example
private Lock lock = new ReentrantLock();
private String threadAcquired = "main";
@Test
void wrongMemoryVisibility() throws InterruptedException {
Thread withDelayedLock = new Thread(() -> {
lock.lock();
try {
threadAcquired = "delayed";
System.out.println("Acquired on delayed");
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
});
lock.lock();
try {
withDelayedLock.start();
Thread.sleep(500);
} finally {
lock.unlock();
}
while (true) {
System.out.println("Currently acquired " + threadAcquired);
if (threadAcquired.equals("delayed")) {
break;
}
}
withDelayedLock.join();
threadAcquired = "main";
}
We print the variable threadAcquired by the main thread while it is changed by the thread with a Delayed lock.
After a few runs a situation where the threadAcquired variable has a stale value on the main thread will appear.
Acquired on delayed
Currently acquired main
We did this on purpose, on a real world problem we should not access variables like threadAcquired without proper synchronisation, for example we should have acquired the lock first, this way the memory would be synchronised and we would have an up to date value.
@Test
void correctMemoryVisibility() throws InterruptedException {
Thread withDelayedLock = new Thread(() -> {
lock.lock();
try {
threadAcquired = "delayed";
System.out.println("Acquired on delayed");
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
});
lock.lock();
try {
withDelayedLock.start();
Thread.sleep(500);
} finally {
lock.unlock();
}
while (true) {
lock.lock();
try {
System.out.println("Currently acquired " + threadAcquired);
if (threadAcquired.equals("delayed")) {
break;
}
} finally {
lock.unlock();
}
}
withDelayedLock.join();
}
That’s all for now, we will later proceed on different type of locks and the Condition interface.
Source: javacodegeeks.com
0 comments:
Post a Comment