The use of the java synchronized keyword

  • 2020-05-09 18:42:39
  • OfStack

0. The lead problem code

        the following code demonstrates a counter, two threads at the same time to accumulate i operation, performed 1000000 times each. We expect the result must be i = 2000000. But after executed multiple times, we will find i value always less than 2000000. This is because, when two threads simultaneously to write i, the result of one thread will cover the other one.


public class AccountingSync implements Runnable {
  static int i = 0;
  public void increase() {
    i++;
  }
 
  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      increase();
    }
  }
 
  public static void main(String[] args) throws InterruptedException {
    AccountingSync accountingSync = new AccountingSync();
 
    Thread t1 = new Thread(accountingSync);
    Thread t2 = new Thread(accountingSync);
 
    t1.start();
    t2.start();
 
    t1.join();
    t2.join();
 
    System.out.println(i);
  }
}

        to fundamentally solve this problem, we must ensure that multiple threads are fully synchronized while operating on i, which means that by the time A writes to i,B cannot not only write, but also read.

1. The role of the synchronized keyword

The function of         keyword synchronized is actually to realize the synchronization between threads. Its job is to lock the synchronized code, so that only one thread can enter the synchronized block every time, so as to guarantee the security between threads.

2. Usage of the synchronized keyword

Specify object lock: lock a given object, enter the synchronized code block to obtain the lock for the given object

Direct action on the instance method: equivalent to locking the current instance and entering the synchronized code block to obtain the lock of the current instance (this requires the creation of Thread with the same instance of Runnable)

Direct action on static methods: equivalent to locking the current class and obtaining the lock of the current class before entering the synchronized block

2.1 specify object locking

The code below         applies synchronized to a given object. One thing to note here is that given object 1 must be static's, otherwise we will not share the object with each other every time new1 threads come out, so the significance of locking will not exist.


public class AccountingSync implements Runnable {
  final static Object OBJECT = new Object();
 
  static int i = 0;
  public void increase() {
    i++;
  }
 
  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      synchronized (OBJECT) {
        increase();
      }
    }
  }
 
  public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(new AccountingSync());
    Thread t2 = new Thread(new AccountingSync());
 
    t1.start();
    t2.start();
 
    t1.join();
    t2.join();
 
    System.out.println(i);
  }
}
2.2 act directly on the instance method

        synchronized key role in instance methods, that is to say when entering increase before () method, a thread must acquire the current instance of the lock. This requires us, in creating Thread instance, to use object instance with a Runnable. Otherwise, the thread lock are not same one instance, lock/synchronization problem is to talk about.


public class AccountingSync implements Runnable {
  static int i = 0;
  public synchronized void increase() {
    i++;
  }
 
  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      increase();
    }
  }
 
  public static void main(String[] args) throws InterruptedException {
    AccountingSync accountingSync = new AccountingSync();
 
    Thread t1 = new Thread(accountingSync);
    Thread t2 = new Thread(accountingSync);
 
    t1.start();
    t2.start();
 
    t1.join();
    t2.join();
 
    System.out.println(i);
  }
}

Notice the first three lines of the main method, indicating the correct use of the keyword applied to the instance method.

2.3 act directly on static methods

        applies the synchronized keyword to the static method, instead of the two threads pointing to the same Runnable method as in the above example, because the method block needs to request the lock of the current class, not the current instance, and the threads can still be synchronized correctly.


public class AccountingSync implements Runnable {
  static int i = 0;
  public static synchronized void increase() {
    i++;
  }
 
  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      increase();
    }
  }
 
  public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(new AccountingSync());
    Thread t2 = new Thread(new AccountingSync());
 
    t1.start();
    t2.start();
 
    t1.join();
    t2.join();
 
    System.out.println(i);
  }
}

Lock incorrectly

From the above example, we know that if we need to apply a counter, we will naturally need to lock the counter in order to ensure the correctness of the data. Therefore, we may write the following code:


public class BadLockOnInteger implements Runnable {
  static Integer i = 0;
  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      synchronized (i) {
        i++;
      }
    }
  }
 
  public static void main(String[] args) throws InterruptedException {
    BadLockOnInteger badLockOnInteger = new BadLockOnInteger();
 
    Thread t1 = new Thread(badLockOnInteger);
    Thread t2 = new Thread(badLockOnInteger);
 
    t1.start();
    t2.start();
 
    t1.join();
    t2.join();
 
    System.out.println(i);
  }
}

      when we run the above code, we will find that the output i is very small, which indicates that threads are not safe.

        to explain this, start with Integer: in Java,Integer is an immutable object, like String1, where once object 1 is created, it cannot be modified. If you have an Integer=1, it will always be 1. Only 1 Integer can be recreated. Every time after i++, it is equivalent to calling Integer's valueOf method. Let's take a look at the source code of Integer's valueOf method:


public static Integer valueOf(int i) {
  if (i >= IntegerCache.low && i <= IntegerCache.high)
    return IntegerCache.cache[i + (-IntegerCache.low)];
  return new Integer(i);
}

        Integer.valueOf() is actually a factory method, which tends to return a new Integer object and copy the value back to i;

        so, we know the reason for the problem, because between multiple threads, since i++,i points to a new object, the thread may load a different instance of the object each time it locks.


Related articles: