-
ReentrantLock, lockInterruptibly, and tryLockStaticPL/JAVA 2020. 2. 27. 11:02
1. Overview
Reentrant Locks are provided in Java to provide synchronization with greater flexibility.
2. ReentrantLock
2.1 How to use
Lock lockObject = new ReentrantLock(); public int use() throws SomException { lockObject.lock(); try{ someOperations(); return value; } finally { // Ensure releasing lock even though exception occured between locking section lockObject.unlock(); } }
2.2 Why use
2.2.1 For this complexity, we are rewarded with more control over the lock and more lock operations.
- getQueueuedThreads(): Returns a list of threads waiting to acquire a lock
- getOwner(): Returns the thread that currently owns the lock
- isHeldByCurrentThread(): Queries if the lock is held by the current thread
- isLocked(): Queries if the lock is held by any thread
2.2.2 Control over lock's fairness
May reduce the throughput of the application because fairness is cost.
ReentrantLock(true)
3. ReentrantLock.lockInterruptibly()
3.1 Motivation
public lcass SomeThread extends Thread { @Override public void run() { while(true) { try { lockObject.lockInterruptibly(); } catch(InterruptedException e) { // cleanUpAndExit(); } } } } ... someThread.interrupt();
If we want to stop the thread from waiting on the lock and interrupted by calling the interrupt method from another thread then the suspended thread would wake up and jump to the catch block inside the catch block we can do some cleanup and shut down the thread gracefully
3.2 Usage
- Watchdog for deadlock detection and recovery
- Waking up threads to do clean and close the application
4. ReentrantLock.tryLock()
4.1 Why
boolean tryLock(long timeout, TimeUnit unit);
- Returns true and acquire a lock if available
- Returns false and does not get suspended, if the lock is unavailable
try lock operation tries to acquire the log just like the regular lock and if the lock is available and the try walk will simply acquire the lock and also return true. However, if the lock is currently unavailable instead of blocking the thread the method simply returns false and moves on to the next instruction.
... if(lockObject.tryLock()) { try { useResource(); } finally { lockObject.unlock(); } } else { ... }
Instead, the try walk would return false immediately which would let us know that the lock does not belong to us and we must not use the shared resource. The threads can jump to execute some different code in the else statement that does not need to use the shared resource and in the future may come back to try and acquire the lock again.
4.2 Usage
It is used where suspending a thread on a lock() method is unacceptable such as Realtime applications
- Video/Image processing
- High Speed/Low latency trading systems
- User Interface applications
5. Example
public class reentrantlockDemo extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { primaryStage.setTitle("Cryptocurrency Prices"); GridPane grid = createGrid(); Map<String, Label> cryptoLabels = createCryptoPriceLabels(); addLabelsToGrid(cryptoLabels, grid); double width = 300; double height = 250; StackPane root = new StackPane(); Rectangle background = createBackgroundRectangleWithAnimation(width, height); root.getChildren().add(background); root.getChildren().add(grid); primaryStage.setScene(new Scene(root, width, height)); PricesContainer pricesContainer = new PricesContainer(); PriceUpdater priceUpdater = new PriceUpdater(pricesContainer); AnimationTimer animationTimer = new AnimationTimer() { @Override public void handle(long now) { // pricesContainer.getLockObject().lock(); if (pricesContainer.getLockObject().tryLock()) { try { Label bitcoinLabel = cryptoLabels.get("BTC"); bitcoinLabel.setText(String.valueOf(pricesContainer.getBitcoinPrice())); Label etherLabel = cryptoLabels.get("ETH"); etherLabel.setText(String.valueOf(pricesContainer.getEtherPrice())); Label litecoinLabel = cryptoLabels.get("LTC"); litecoinLabel.setText(String.valueOf(pricesContainer.getLitecoinPrice())); Label bitcoinCashLabel = cryptoLabels.get("BCH"); bitcoinCashLabel.setText(String.valueOf(pricesContainer.getBitcoinCashPrice())); Label rippleLabel = cryptoLabels.get("XRP"); rippleLabel.setText(String.valueOf(pricesContainer.getRipplePrice())); } finally { pricesContainer.getLockObject().unlock(); } } } }; addWindowResizeListener(primaryStage, background); animationTimer.start(); priceUpdater.start(); primaryStage.show(); } private void addWindowResizeListener(Stage stage, Rectangle background) { ChangeListener<Number> stageSizeListener = ((observable, oldValue, newValue) -> { background.setHeight(stage.getHeight()); background.setWidth(stage.getWidth()); }); stage.widthProperty().addListener(stageSizeListener); stage.heightProperty().addListener(stageSizeListener); } private Map<String, Label> createCryptoPriceLabels() { Label bitcoinPrice = new Label("0"); bitcoinPrice.setId("BTC"); Label etherPrice = new Label("0"); etherPrice.setId("ETH"); Label liteCoinPrice = new Label("0"); liteCoinPrice.setId("LTC"); Label bitcoinCashPrice = new Label("0"); bitcoinCashPrice.setId("BCH"); Label ripplePrice = new Label("0"); ripplePrice.setId("XRP"); Map<String, Label> cryptoLabelsMap = new HashMap<>(); cryptoLabelsMap.put("BTC", bitcoinPrice); cryptoLabelsMap.put("ETH", etherPrice); cryptoLabelsMap.put("LTC", liteCoinPrice); cryptoLabelsMap.put("BCH", bitcoinCashPrice); cryptoLabelsMap.put("XRP", ripplePrice); return cryptoLabelsMap; } private GridPane createGrid() { GridPane grid = new GridPane(); grid.setHgap(10); grid.setVgap(10); grid.setAlignment(Pos.CENTER); return grid; } private void addLabelsToGrid(Map<String, Label> labels, GridPane grid) { int row = 0; for (Map.Entry<String, Label> entry : labels.entrySet()) { String cryptoName = entry.getKey(); Label nameLabel = new Label(cryptoName); nameLabel.setTextFill(Color.BLUE); nameLabel.setOnMousePressed(event -> nameLabel.setTextFill(Color.RED)); nameLabel.setOnMouseReleased((EventHandler) event -> nameLabel.setTextFill(Color.BLUE)); grid.add(nameLabel, 0, row); grid.add(entry.getValue(), 1, row); row++; } } private Rectangle createBackgroundRectangleWithAnimation(double width, double height) { Rectangle backround = new Rectangle(width, height); FillTransition fillTransition = new FillTransition(Duration.millis(1000), backround, Color.LIGHTGREEN, Color.LIGHTBLUE); fillTransition.setCycleCount(Timeline.INDEFINITE); fillTransition.setAutoReverse(true); fillTransition.play(); return backround; } @Override public void stop() { System.exit(0); } public static class PricesContainer { private Lock lockObject = new ReentrantLock(); private double bitcoinPrice; private double etherPrice; private double litecoinPrice; private double bitcoinCashPrice; private double ripplePrice; public Lock getLockObject() { return lockObject; } public void setLockObject(Lock lockObject) { this.lockObject = lockObject; } public double getBitcoinPrice() { return bitcoinPrice; } public void setBitcoinPrice(double bitcoinPrice) { this.bitcoinPrice = bitcoinPrice; } public double getEtherPrice() { return etherPrice; } public void setEtherPrice(double etherPrice) { this.etherPrice = etherPrice; } public double getLitecoinPrice() { return litecoinPrice; } public void setLitecoinPrice(double litecoinPrice) { this.litecoinPrice = litecoinPrice; } public double getBitcoinCashPrice() { return bitcoinCashPrice; } public void setBitcoinCashPrice(double bitcoinCashPrice) { this.bitcoinCashPrice = bitcoinCashPrice; } public double getRipplePrice() { return ripplePrice; } public void setRipplePrice(double ripplePrice) { this.ripplePrice = ripplePrice; } } public static class PriceUpdater extends Thread { private PricesContainer pricesContainer; private Random random = new Random(); public PriceUpdater(PricesContainer pricesContainer) { this.pricesContainer = pricesContainer; } @Override public void run() { while(true) { pricesContainer.getLockObject().lock(); try { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } pricesContainer.setBitcoinPrice(random.nextInt(20000)); pricesContainer.setBitcoinCashPrice(random.nextInt(5000)); pricesContainer.setLitecoinPrice(random.nextInt(500)); pricesContainer.setEtherPrice(random.nextInt(2000)); pricesContainer.setRipplePrice(random.nextDouble()); } finally { pricesContainer.getLockObject().unlock(); } try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
6. Reference
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantLock.html
'StaticPL > JAVA' 카테고리의 다른 글
Semaphore (0) 2020.02.27 ReentrantReadWriteLock (0) 2020.02.27 synchronized (0) 2020.02.27 Atomic Variables (0) 2020.02.26 Access Modifier (0) 2020.02.23