1. Simulate the concurrency of multiple ATM machines accessing money simultaneously using multithreading
class Account { //holder private string _holderName; public string HolderName { get { return _holderName; } set { _holderName = value; } } //Password private string _password; public string Password { get { return _password; } set { _password = value; } } //Account balance private float _amount; public float Amount { get { return _amount; } set { _amount = value; } } /// <summary> /// Constructor /// </summary> /// <param name="holdderName"></param> /// <param name="password"></param> /// <param name="amount"></param> public Account(string holdderName, string password, float amount) { this._holderName = holdderName; this._password = password; this._amount = amount; } //deposit public void Deposit(float amount) { if (amount > 0) { this._amount = this._amount + amount; } } //Withdraw money public void Withdraw(float amount) { if (this._amount >= amount && amount > 0) { this._amount = this._amount - amount; } } //Get Balance public float CheckBalance() { return this._amount; } } class ATM { //Account number public Account account; //deposit public void Deposit(float amount) { account.Deposit(amount); } //Withdraw money public void WithDraw(float amount) { account.Withdraw(amount); } //Get Balance public float GetBalance() { return account.CheckBalance(); } }
class Program { Account acc = new Account("Zhang Lan", "0000", 5000); static void Main(string[] args) { Thread[] threads = new Thread[10]; Program p = new Program(); for (int i = 0; i < 10; i++) { threads[i] = new Thread(new ThreadStart(p.Run)); threads[i].Name = "thread" + (i + 1); } foreach (Thread t in threads) { t.Start(); } Console.ReadKey(); } public void Run() { ATM atm = new ATM(); atm.account = acc; Console.WriteLine(Thread.CurrentThread.Name + ": Query Current" + atm.GetBalance()); Console.WriteLine(Thread.CurrentThread.Name + ": Draw 2000"); //Withdraw money atm.WithDraw(2000); Thread.Sleep(100); Console.WriteLine(Thread.CurrentThread.Name + ": Query Current" + atm.GetBalance()); Console.WriteLine(Thread.CurrentThread.Name + ": Deposit 2000"); //deposit atm.Deposit(2000); Console.WriteLine(Thread.CurrentThread.Name + ": Query Current" + atm.GetBalance()); } }
We can find that 10 people deposit 2000 and withdraw 2000 at the same time, and the final balance should be unchanged, but the results are confusing, indicating that multiple threads are alternating, and concurrency problems occur.
2. Solutions
(1) Use lock to solve concurrency problems
The lock keyword marks a statement block as a critical zone by acquiring a mutex on a given object, executing the statement, and releasing the lock.The form of this statement is as follows:
Object thisLock = new Object();
lock (thisLock)
{
// Critical code section.
}
The lock keyword ensures that when one thread is in a critical zone of code, another thread does not enter that critical zone.If another thread attempts to enter locked code, it will wait (that is, be blocked) until the object is released.
The Lock keyword means: code that allows you to define a thread synchronization, and its syntax is:
Put some code in the lock block and it's safe
The code block to add here is the operation code for withdrawal and deposit put in the lock block
Define an object of type object first
Next we put the code for the operation account in the lock block:
class Program { public static readonly object _lockObject = new object(); Account acc = new Account("Zhang Lan", "0000", 5000); static void Main(string[] args) { Thread[] threads = new Thread[10]; Program p = new Program(); for (int i = 0; i < 10; i++) { threads[i] = new Thread(new ThreadStart(p.Run)); threads[i].Name = "thread" + (i + 1); } foreach (Thread t in threads) { t.Start(); } Console.ReadKey(); } public void Run() { ATM atm = new ATM(); atm.account = acc; //Lock quota-related operation codes lock (_lockObject) { Console.WriteLine(Thread.CurrentThread.Name + ": Query Current" + atm.GetBalance()); Console.WriteLine(Thread.CurrentThread.Name + ": Draw 2000"); //Withdraw money atm.WithDraw(2000); Thread.Sleep(100); Console.WriteLine(Thread.CurrentThread.Name + ": Query Current" + atm.GetBalance()); Console.WriteLine(Thread.CurrentThread.Name + ": Deposit 2000"); //deposit atm.Deposit(2000); Console.WriteLine(Thread.CurrentThread.Name + ": Query Current" + atm.GetBalance()); } } }
No concurrency problems with running results!
Thread 1 operates on the account, and when it is finished, other threads can operate on the account.That is, after each thread has performed a series of operations such as inquiry, withdrawal and deposit on the account, then other threads can operate on the account, ensuring that the balance of the account is no longer problematic.
(2) Use Monitor to solve concurrency problems
Monitor class provides synchronous access to objects
The Monitor class controls access to objects by granting them lock to a single thread.
Object locks provide the ability to restrict access to blocks of code, often referred to as critical zones.When a thread owns a lock on an object, no other thread can acquire the lock.You can also use Monitor to ensure that no other thread is allowed access to the application code section being executed by the owner of the lock unless another thread is executing the code using another locked object.
Use Monitor to lock objects (that is, reference types) instead of value types.
Two methods of using Monitor to solve concurrency problems are the TryEnter and Exit methods
The TryEnter method acquires an exclusive lock on the specified object and has several overloaded versions. This is a case with two parameters. The method returns a bool value. If the current thread acquires the lock without blocking it, it returns true; otherwise, it returns false.
The Exit method releases an exclusive lock on the specified object with an object-type parameter.
class Program { public static readonly object _lockObject = new object(); Account acc = new Account("Zhang Lan", "0000", 5000); static void Main(string[] args) { Thread[] threads = new Thread[10]; Program p = new Program(); for (int i = 0; i < 10; i++) { threads[i] = new Thread(new ThreadStart(p.Run)); threads[i].Name = "thread" + (i + 1); } foreach (Thread t in threads) { t.Start(); } } public void Run() { ATM atm = new ATM(); atm.account = acc; //Lock quota-related operation codes try { if (Monitor.TryEnter(_lockObject, -1)) { Console.WriteLine(Thread.CurrentThread.Name + ": Query Current" + atm.GetBalance()); Console.WriteLine(Thread.CurrentThread.Name + ": Draw 2000"); //Withdraw money atm.WithDraw(2000); Thread.Sleep(100); Console.WriteLine(Thread.CurrentThread.Name + ": Query Current" + atm.GetBalance()); Console.WriteLine(Thread.CurrentThread.Name + ": Deposit 2000"); //deposit atm.Deposit(2000); Console.WriteLine(Thread.CurrentThread.Name + ": Query Current" + atm.GetBalance()); } } finally { Monitor.Exit(_lockObject); } } }