In Java, the keyword synchronized can be used for thread synchronization control to achieve sequential access to key resources, and avoid data inconsistency caused by multi-threaded concurrent execution. The principle of synchronized is an object monitor (lock). Only the thread that acquires the monitor can continue to execute, otherwise the thread will wait to acquire the monitor. Each object or class in Java has a lock associated with it. For an object, it monitors the instance variable of this object. For a class, it monitors the class variable (a class itself is an object of the class Class, so the lock associated with the class is also an object lock). There are two ways to use synchronized keywords: synchronized method and synchronized block. Both monitoring areas are associated with an introduced object. When it reaches this monitoring area, the JVM will lock the reference object, and when it leaves, the lock on the reference object will be released (the JVM will release the lock when there is an exception exit). Object locks are internal mechanisms of JVM. You only need to write synchronization methods or synchronization blocks. When operating monitoring areas, the JVM will automatically acquire or release the lock.
Example 1
Let’s first look at the first example. In Java, there is only one critical area of the same object that is allowed to be accessed at the same time (all non-static synchronized methods):
package concurrency;public class Main8 { public static void main(String[] args) { Account account = new Account(); account.setBalance(1000); Company company = new Company(account); Thread companyThread = new Thread(company); Bank bank = new Bank(account); Thread bankThread = new Thread(bank); System.out.printf("Account : Initial Balance: %f/n", account.getBalance()); companyThread.start(); bankThread.start(); try { //join() method waits for these two threads to complete companyThread.join(); bankThread.join(); System.out.printf("Account : Final Balance: %f/n", account.getBalance()); } catch (InterruptedException e) { e.printStackTrace(); } }} /*Account*/class Account{ private double balance; /*Add incoming data to balance balance*/ public synchronized void addAmount(double amount){ double tmp = balance; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } tmp += amount; balance = tmp; } /*Deduct incoming data from balance balance*/ public synchronized void subtractAmount(double amount){ double tmp = balance; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } tmp -= amount; balance = tmp; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; }} /*Bank*/class Bank implements Runnable{ private Account account; public Bank(Account account){ this.account = account; } @Override public void run() { for (int i = 0; i < 100; i++) { account.subtractAmount(1000); } }} /*Company*/class Company implements Runnable{ private Account account; public Company(Account account){ this.account = account; } @Override public void run() { for (int i = 0; i < 100; i++) { account.addAmount(1000); } }}You have developed a simulation application for bank accounts that can top up and deduct balances. This program recharges the account by calling the addAmount() method 100 times, depositing 1,000 each time; then deducts the account balance by calling the subtractAmount() method 100 times, deducting 1,000 each time; we expect the final balance of the account to be equal to the initial balance, and we implement it through the synchronized keyword.
If you want to view the concurrent access problem of shared data, you only need to delete the synchronized keywords in the addAmount() and subtractAmount() method declarations. Without the synchronized keyword, the printed balance value is not consistent. If you run this program multiple times, you will get different results. Because the JVM does not guarantee the execution order of threads, each time it runs, the threads will read and modify the account balance in different orders, resulting in different final results.
An object's method is declared using the synchronized keyword and can only be accessed by one thread. If thread A is executing a synchronization method syncMethodA(), thread B wants to execute other synchronization methods syncMethodB() of this object, thread B will be blocked until thread A completes access. But if thread B accesses different objects of the same class, neither thread will be blocked.
Example 2
Demonstrate the problem that static synchronized methods and non-static synchronized methods on the same object can be accessed by multiple threads at the same time. Verify it.
package concurrency;public class Main8 { public static void main(String[] args) { Account account = new Account(); account.setBalance(1000); Company company = new Company(account); Thread companyThread = new Thread(company); Bank bank = new Bank(account); Thread bankThread = new Thread(bank); System.out.printf("Account : Initial Balance: %f/n", account.getBalance()); companyThread.start(); bankThread.start(); try { //join() method waits for these two threads to complete companyThread.join(); bankThread.join(); System.out.printf("Account : Final Balance: %f/n", account.getBalance()); } catch (InterruptedException e) { e.printStackTrace(); } }} /*Account*/class Account{ /*Change it to a static variable here*/ private static double balance = 0; /*Add incoming data to the balance balance, note that it is modified with static*/ public static synchronized void addAmount(double amount){ double tmp = balance; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } tmp += amount; balance = tmp; } /*Deduct the incoming data from the balance balance*/ public synchronized void subtractAmount(double amount){ double tmp = balance; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } tmp -= amount; balance = tmp; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; }} /*Bank*/class Bank implements Runnable{ private Account account; public Bank(Account account){ this.account = account; } @Override public void run() { for (int i = 0; i < 100; i++) { account.subtractAmount(1000); } }} /*Company*/class Company implements Runnable{ private Account account; public Company(Account account){ this.account = account; } @Override public void run() { for (int i = 0; i < 100; i++) { account.addAmount(1000); } }}I just added the static keyword to modify the balance in the previous example, and the addAmount() method can also modify the static keyword. You can test the execution results yourself, and each execution will have a different result!
Some summary: