Finally, the project is published and run on the server. The server has implemented multithreading, so what we need to pay attention to when writing the program is the safety of the data in the project in the multithreaded concurrent environment.
Let's start with an example: (just an example, ignoring life)
A and B operate a bank account with a balance of 1w at the same time. A first goes to the ATM to get 1w, and then there is a network delay at this time, and the bank account is not updated. The current balance is 0. Then B goes to the manual service window to query the balance and finds 1w, and then B takes 1w again.
This is definitely not allowed. At this time, it is a security problem caused by multithreading concurrency.
1. Thread unsafe conditions
- Multithreading concurrency;
- Shared data;
- Modified shared data.
Unsafe problems in simulated bank withdrawal cases:
BankAcount class:
package com.dh.threadsafe; public class BankAccount { private String AccountNo; private double balance; public BankAccount(String accountNo, double balance) { AccountNo = accountNo; this.balance = balance; } public String getAccountNo() { return AccountNo; } public void setAccountNo(String accountNo) { AccountNo = accountNo; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } //Withdrawal method public void withDrawMoney(double money){ //Account balance double beforeBalance = this.getBalance(); //withdraw money double afterBalance = balance - money; //Sleep here for 1s to ensure that the account balance is still 10000 when t2 is operated try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //Modify account balance this.setBalance(afterBalance); System.out.println(Thread.currentThread().getName()+"withdraw money"+money+",The account balance is:"+afterBalance); } }
Thread class:
package com.dh.threadsafe; public class MyThread extends Thread { private BankAccount bankAccount; public MyThread(BankAccount bankAccount) { this.bankAccount = bankAccount; } @Override public void run() { //Call withdrawal method bankAccount.withDrawMoney(5000); } }
Test class:
package com.dh.threadsafe; public class Test { public static void main(String[] args) { BankAccount account = new BankAccount("001", 10000); MyThread t1 = new MyThread(account); MyThread t2 = new MyThread(account); t1.setName("t1"); t2.setName("t2"); t1.start(); t2.start(); } }
result:
t1 Withdrawal 5000.0,Account balance: 5000.0 t2 Withdrawal 5000.0,Account balance: 5000.0
So it was a mess. t1 and t2 withdrew 10000 yuan, but the account balance still showed 5000 yuan.
2. How to solve thread safety problems?
Analyzing the causes of thread insecurity can be easily solved:
As long as it is ensured that A withdraws the money and the balance in the bank account changes, B is allowed to operate the balance of the account.
Thread queuing is the thread synchronization mechanism.
The concepts of synchronization and asynchrony are involved here:
- Asynchronous: thread t1 and thread t2 execute respectively. t1 does not care about the execution of t2 and t2 does not care about the execution of t1. (i.e. multithreading concurrency, high efficiency and low security);
- Synchronization: when thread t2 executes, you must wait for thread t1 to finish executing. (that is, threads are queued for execution, with low efficiency and high security).
(safety is the main consideration)
3. Achieve thread safety
(1) synchronized code block
//grammar synchronized(){ //() fill in the resources shared by threads that need to realize thread synchronization //Thread synchronization code block }
The modification code is:
//Withdrawal method public void withDrawMoney(double money) { synchronized (this) { //Not necessarily this, as long as it is a resource shared by the queuing thread //Account balance double beforeBalance = this.getBalance(); //withdraw money double afterBalance = balance - money; //Sleep here for 1s to ensure that the account balance is still 10000 when operating B try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //Modify account balance this.setBalance(afterBalance); System.out.println(Thread.currentThread().getName() + "withdraw money" + money + ",The account balance is:" + afterBalance); } }
result:
t1 Withdrawal 5000.0,Account balance: 5000.0 t2 Withdraw 5000.0,Account balance: 0.0
Code execution principle:
Premise: every object in Java has an object lock, which is actually an identification.
- Assuming that t1 thread and t2 thread are concurrent, one thread will execute the code first and one thread will execute the code later;
- Assuming that the t1 thread executes the code first, when the t1 thread encounters the synchronized keyword, it will automatically find the lock of the shared object in (). After finding it, it will occupy the lock, and then execute the synchronization code in the synchronization code block (it will always occupy the lock during program execution). Until the execution of the synchronization code block ends, it will release the lock.
- Assuming that t1 is still running the synchronized code block and t2 thread also encounters the synchronized keyword, it will automatically find the lock of the shared object in (). However, at this time, the lock is occupied by t1 thread, and t2 thread can only wait for the lock outside the synchronized code block. After waiting for the lock, t2 will also occupy the lock, execute the synchronized code block, and return the lock after execution.
In fact, it is also very similar to the idea of queuing up to the bathroom
This involves the concept of lock pool:
(2) Synchronous instance method
Modify the instance with synchronized method: (only applicable when the shared data is this, not flexible)
//Withdrawal method public synchronized void withDrawMoney(double money) { //Account balance double beforeBalance = this.getBalance(); //withdraw money double afterBalance = balance - money; //Sleep here for 1s to ensure that the account balance is still 10000 when operating B try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //Modify account balance this.setBalance(afterBalance); System.out.println(Thread.currentThread().getName() + "withdraw money" + money + ",The account balance is:" + afterBalance); }
result:
t1 Withdrawal 5000.0,Account balance: 5000.0 t2 Withdrawal 5000.0,Account balance: 0.0
Expand synchronization range:
The scope of synchronization code block is smaller than that of synchronization instance method; The smaller the synchronization range, the higher the efficiency.
However, when the shared data is this and the whole method body needs to be synchronized, it is recommended to use the synchronized instance method. The advantage is less code.
(3) Synchronous static method
When synchronizing static methods, you are looking for class locks: there are 100 objects and only one class lock. 100 objects, 100 object locks.
Modify static methods with synchronized.
(examples are not given here, but will be given in the next synchronized interview question)
4. Which variables have thread safety problems?
There are three variables in java:
- Local variables are stored in the stack and not shared by threads;
- Instance variables are stored in the heap and shared by threads;
- Static variables are stored in the method area and shared by threads.
Only instance variables and static variables (both member variables) have thread safety problems; There is no thread safety problem with local variables.
Constants are also thread safe.
5. Disadvantages of synchronized
synchronized will make the execution efficiency of the program low, the throughput of the system is low, and the user experience is poor (especially some second kill, ticket grabbing and other functions). synchronized can only be used when you have to.
- If local variables can be used, try to use local variables instead of instance variables and static variables;
- If you must use instance variables, try to use as many objects as possible, preferably one object per thread;
- If you can't use local variables or multiple objects, you can only use synchronized.