What is the purpose of passing a parameter to a synchronized block?

I know that

When you synchronize a code block, you specify which object lock you want to use as a lock, so that you can, for example, use some third-party object as a lock for this code fragment. This gives you the ability to have more than one lock to synchronize code within a single object.

However, I do not understand the need to pass an argument to a block. Since it doesn't matter if I pass in an instance of String, some case of a random class for a synchronized block, since the synchronized block works fine, regardless of the parameter passed to the block.

So my question is , if in any case the synchronized block stops two threads from entering the critical section at the same time. Then why is it necessary to pass an argument. (I mean, by default, locks on some random object).

I hope I correctly formulated my question.

I tried the following example with random parameters related to a synchronized block.

public class Launcher { public static void main(String[] args) { AccountOperations accOps=new AccountOperations(); Thread lucy=new Thread(accOps,"Lucy"); Thread sam=new Thread(accOps,"Sam"); lucy.start(); sam.start(); } } 

Using a non-static synchronized block:

 public class AccountOperations implements Runnable{ private Account account = new Account(); public void run(){ for(int i=0;i<5;i++){ makeWithdrawal(10); } } public void makeWithdrawal(int amount){ String str="asd" synchronized (str /* pass any non-null object the synchronized block works*/) { if(account.getAmount()>10){ try{ Thread.sleep(5000); }catch(InterruptedException e){ e.printStackTrace(); } account.withdraw(amount); System.out.println(Thread.currentThread().getName()+" has withdrawn 10, current balance "+ account.getAmount()); }else{ System.out.println("Insufficient funds "+account.getAmount()); } } } } 

Using a static synchronized block:

 public class AccountOperations implements Runnable{ private static Account account = new Account(); public void run(){ for(int i=0;i<5;i++){ makeWithdrawal(10); } } public static void makeWithdrawal(int amount){ synchronized (String.class /* pass any class literal synchronized block works*/) { if(account.getAmount()>10){ try{ Thread.sleep(5000); }catch(InterruptedException e){ e.printStackTrace(); } account.withdraw(amount); System.out.println(Thread.currentThread().getName()+" has withdrawn 10, current balance "+ account.getAmount()); }else{ System.out.println("Insufficient funds "+account.getAmount()); } } } } 
+6
source share
2 answers

if in any case the synchronized block stops two threads from entering the critical section at the same time. Then why is it necessary to pass an argument?

The synchronized block decides which threads to stop based on the object you pass to it. The object you are passing in is the identifier of the critical section protected by the synchronized block.

Your program can have many critical sections, all of which can be executed simultaneously with each other. For example, if you have two unrelated collections that you need to access at the same time, you can configure separate critical sections for each collection. Thus, threads will only be stopped when other threads are already accessing the same collection; two different threads accessing two different collections will be allowed simultaneously.

Your first example is non-trivial. The reason it works is because the string object is initialized with a string literal. Due to literal interning, all threads entering the function will receive the same String object, so the synchronized block will correctly protect the critical section.

+4
source

Because it doesn’t matter if I pass an instance of String. Some instances of a random class for a synchronized block, because the synchronized block works fine regardless of the parameter passed to the block.

The purpose of the parameter is binary:

  • This allows you to synchronize other blocks on the same object, so if you have two blocks of code that can change the state of the same object, they do not interfere with each other.

    For instance:

     public void getSum() { int sum = 0; synchronized (this.list) { for (Thingy t : this.list) { sum += t.getValue(); } } return sum; } public void addValue(int value) { synchronized (this.list) { this.list.add(new Thingy(value)); } } 

    It is important here to synchronize both calls to list over streams. We cannot have something calling addValue and stomp on the list while another thread calls getSum .

  • This allows for synchronization with the correct granularity. If you serialize access to an instance-specific resource, then it makes no sense to do this through instances; you must allow multiple threads per block if they work in different instances. This is why you synchronized to this (or, most often, the this field) for an instance-specific resource, or a class (or, most often, a class field) if it was a static resource. Similarly, there is no need to sync to this if you only need to protect a specific field.

    For instance:

     // (In MyClass) public void getThingySum() { int sum = 0; synchronized (this.thingyList) { for (Thingy t : this.thingyList) { sum += t.getValue(); } } return sum; } public void addThingy(Thingy t) { synchronized (this.thingyList) { this.thingyList.add(t); } } public void getNiftySum() { int sum = 0; synchronized (this.niftyList) { for (Nifty n : this.niftyList) { sum += n.getValue(); } } return sum; } public void addNifty(Nifty n) { synchronized (this.niftyList) { this.niftyList.add(t); } } 

    Here we synchronize access to this.thingyList on this.thingyList , not this or MyClass.class . This is normal if one thread calls getThingySum and another thread calls addNifty , so synchronization on this will be excessive.


Repeat the str example:

 public void makeWithdrawal(int amount){ String str="asd" synchronized (str /* pass any non-null object the synchronized block works*/) { if(account.getAmount()>10){ try{ Thread.sleep(5000); }catch(InterruptedException e){ e.printStackTrace(); } account.withdraw(amount); System.out.println(Thread.currentThread().getName()+" has withdrawn 10, current balance "+ account.getAmount()); }else{ System.out.println("Insufficient funds "+account.getAmount()); } } } 

The comment is incorrect, any non- null instance will not adequately protect this code. The reason this above seems to work is because of string interning. The same String instance is used by all threads, because string literals are automatically placed in the intern string. (This means that you re-synchronize, it is JVM-wide, not instance specific.) This way it works, but not because it is just any object. If you changed it:

 String str = "asd"; 

to

 Object o = new Object(); 

and synchronized with this, it will do nothing to serialize access to the account.

In your example, the correct thing to sync is this.account .

+11
source

Source: https://habr.com/ru/post/985987/


All Articles