ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • ReentrantLock, lockInterruptibly, and tryLock
    StaticPL/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

    https://www.geeksforgeeks.org/reentrant-lock-java/

    '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

    댓글

Designed by Tistory.