You can use the keyword synchronized in Java for thread synchronization control to achieve sequential access to key resources and avoid problems such as data inconsistency caused by concurrent execution of multiple threads. The principle of synchronized is the object monitor (lock). Only the thread that obtains the monitor can continue to execute, otherwise the thread will wait to obtain the monitor. Each object or class in Java has a lock associated with it. For an object, what is monitored is the instance variable of the object. For a class, what is monitored is the class variable (a class itself is an object of class Class, So the lock associated with the class is also an object lock). There are two ways to use the synchronized keyword: synchronized method and synchronized block. Both monitoring areas are associated with an introduced object. When arriving in this monitoring area, the JVM will lock the reference object and release the lock on the reference object when leaving (the JVM will release the lock when there is an exception exit). ). Object lock is an internal mechanism of JVM. You only need to write a synchronization method or synchronization block. When operating the monitoring area, the JVM will automatically acquire or release the lock.
Example 1
Let’s look at the first example first. In Java, only one critical section of the same object is allowed to be accessed at the same time (all are 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()方法等待这两个线程运行完成 companyThread.join(); bankThread.join(); System.out.printf("Account : Final Balance: %f\n", account.getBalance()); } catch (InterruptedException e) { e.printStackTrace(); } } }
/*帐户*/ class Account{ private double balance; /*将传入的数据加到余额balance中*/ public synchronized void addAmount(double amount){ double tmp = balance; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } tmp += amount; balance = tmp; } /*将传入的数据从余额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; } }
/*银行*/ 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); } } }
/*公司*/ 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 bank account simulation application that can add funds to and deduct funds from the balance. This program recharges the account by calling the addAmount() method 100 times, depositing 1000 each time; then deducts the account balance by calling the subtractAmount() method 100 times, deducting 1000 each time; we expect the final balance of the account to be the same as the initial balance Equality is achieved through the synchronized keyword.
If you want to check the concurrent access problem of shared data, you only need to delete the synchronized keyword in the addAmount() and subtractAmount() method declarations. Without the synchronized keyword, the printed balance values are inconsistent. If you run this program multiple times, you will get different results. Because the JVM does not guarantee the execution order of threads, the threads will read and modify the account balance in a different order each time it is run, resulting in a different final result.
The method of an object is declared using the synchronized keyword and can only be accessed by one thread. If thread A is executing a synchronization method syncMethodA(), and 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
Demonstrates the problem that static synchronized methods and non-static synchronized methods on the same object can be accessed by multiple threads at the same point in 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()方法等待这两个线程运行完成 companyThread.join(); bankThread.join(); System.out.printf("Account : Final Balance: %f\n", account.getBalance()); } catch (InterruptedException e) { e.printStackTrace(); } } }
/*帐户*/ class Account{ /*这里改为静态变量*/ private static double balance = 0; /*将传入的数据加到余额balance中,注意是用static修饰过的*/ public static synchronized void addAmount(double amount){ double tmp = balance; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } tmp += amount; balance = tmp; } /*将传入的数据从余额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; } }
/*银行*/ 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); } } }
/*公司*/ 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 modified the balance in the previous example by adding the static keyword. The addAmount() method can also be modified with the static keyword. You can test the execution results yourself. Each execution will have different results!
Some summary:
synchronized is implemented through software (JVM) and is simple and easy to use. Even with Lock after JDK5, it is still widely used.
Synchronized is actually unfair. New threads may get the monitor immediately, and threads that have been waiting in the waiting area for a long time may wait again. However, this preemption method can prevent starvation.
synchronized only locks are only associated with one condition (whether to acquire the lock), which is inflexible. Later, the combination of Condition and Lock solved this problem.
When multiple threads compete for a lock, the remaining threads that have not obtained the lock can only keep trying to obtain the lock without interruption. High concurrency will lead to performance degradation. ReentrantLock's lockInterruptibly() method can prioritize responding to interrupts. If a thread waits too long, it can interrupt itself, and then ReentrantLock responds to the interrupt and no longer lets the thread continue to wait. With this mechanism, when using ReentrantLock, there will be no deadlock like synchronized.
For more examples of Java using the synchronized modification method to synchronize threads, please pay attention to the PHP Chinese website for related articles!