银行取款[多线程]{使用重入锁Lock接口ReentrantLock锁确保线程同步}

简介: 经典例子:老婆(朱丽叶)老公(罗密欧),使用银行卡和存折,或者网银等,同时对同一账户操作的安全问题。  此处用多线程实现,同时取款的模拟实现,使用使用Lock接口ReentrantLock锁确保线程同步,查看取款安全隐患问题,代码如下: ---------------------------------------------------------------------------
经典例子:老婆(朱丽叶)老公(罗密欧),使用银行卡和存折,或者网银等,同时对同一账户操作的安全问题。

 此处用多线程实现,同时取款的模拟实现,使用使用Lock接口ReentrantLock锁确保线程同步,查看取款安全隐患问题,代码如下:

------------------------------------------------------------------------------------------------------------------------------------------------

 * 线程同步 :使用重入锁ReentrantLock锁,代码编写,实现线程同步
 * ReentrantLock 拥有Synchronized相同的并发性和内存语义(ReentrantLock可实现Synchronized的所有功能,有更精确的线程语义和更好的性能)
 * synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定;
 * Lock不会自动释放锁,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unlock()放到finally{}中
 * 在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力
 * 适用:在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock
 *        在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态
 *ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用
 *ReentrantLock在传递锁等锁中,例如"手拉手",更适用。

------------------------------------------------------------------------------------------------------------------------------------------------

private void makeWithdraw(int amount){
  //add lock
  lock.lock();
  try {
   //code
  } catch (InterruptedException e) {
   //code
  }finally{
   //release lock must in the finally
   lock.unlock();
  }
}

银行账户:

package com.tsxs.bank;

public class BankAccount {
	//余额
	private int balance = 500;
	//查询
	public int getBalance(){
		return banlance;
	}
	//取款
	public void withdraw(int amount){
		banlance = banlance - amount;
	}
	//存款
	public void deposit(int amount){
		banlance = banlance + amount;
	}
}

使用ReentrantLock锁,代码: 

package com.tsxs.syncmethods;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import com.tsxs.bank.BankAccount;
/**
 * 此线程类实现Runnable接口<br />
 * 线程同步 :使用ReentrantLock锁,代码编写,实现线程同步<br />
 * ReentrantLock 拥有Synchronized相同的并发性和内存语义(ReentrantLock可实现Synchronized的所有功能,有更精确的线程语义和更好的性能)。<br />
 * synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定;
 * Lock不会自动释放锁,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unlock()放到finally{}中。<br />
 *适用:在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock.
 *        在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;<br />
 *Lock在传递锁等锁中,例如"手拉手",更适用。
 * */
public class CodeLock implements Runnable{
	//所有Thread多线程线程都共享Runnable(接口对象)和account对象
	private BankAccount account = new BankAccount();
	//声明锁,jdk1.5
    Lock lock = new ReentrantLock();
	@Override
	public void run() {
		for(int i = 0; i< 5; i++){			//总共取款5次
			makeWithdraw(100);			//每次取款100
			if(account.getBalance() < 0){
				System.out.println(""+Thread.currentThread().getName()+"   透支了!");
			}
		}
	}
	
	/**
	 * makeWithdraw 账户取款
	 * @param amount 取款金额<br />
	 * 打印log记录取款过程
	 * */
	private void makeWithdraw(int amount){
		//add lock
		lock.lock();
		try {
			if(account.getBanlance() >= amount){			//如果余额足够则取款
				System.out.println(""+Thread.currentThread().getName()+"   准备取款!");
				Thread.sleep(500);
				account.withdraw(amount);
				System.out.println(""+Thread.currentThread().getName()+"   完成"+amount+"取款,余额为"+account.getBalance());			}else{			//余额不足则提示
				System.out.println(""+"余额不足以支付"+Thread.currentThread().getName()+amount+"   的取款,余额为"+account.getBalance());
			}
		} catch (InterruptedException e) {
			System.out.println(Thread.currentThread().getName()+"   准备取款,等待0.5s线程中断!"+e.getMessage());
		}finally{
			//release lock must in the finally
			lock.unlock();
		}
	}
}

测试代码:

package com.tsxs.test;

import org.junit.Test;

import com.tsxs.syncmethods.CodeLock;
import com.tsxs.syncmethods.NoSync;
import com.tsxs.syncmethods.SyncBlock;
import com.tsxs.syncmethods.SyncMethod;

public class TreadSyncTest {

//	@Test
//	public void test() {
/*Junit不适合多线程并发测试。
    因为线程还在激活状态的时候,Junit已经执行完成。
	在Junit的TestRunner中,它没有被设计成搜寻Runnable实例,
	并且等待这些线程发出报告,它只是执行它们并且忽略了它们的存在。
	综上,不可能在Junit中编写和维护多线程的单元测试。
}*/	
	public static void main(String[] args) {
		//实现Runnable:所有Thread多线程线程都共享Runnable(接口对象)
//		NoSync target =new NoSync();
//		SyncMethod target = new SyncMethod();
//		SyncBlock target = new SyncBlock();
		CodeLock target = new CodeLock();
		//创建李琦和他老婆两个线程实现取款(同时)
		Thread lq = new Thread(target);
		lq.setName("罗密欧");
		Thread lqwf = new Thread(target);
		lqwf.setName("朱丽叶");
		//调用Thread对象的start()方法,启动线程,执行run()方法(OS)
		lq.start();
		lqwf.start();
	}
}


 测试结果:

罗密欧   准备取款!
罗密欧   完成100取款!余额为400
罗密欧   准备取款!
罗密欧   完成100取款!余额为300
朱丽叶   准备取款!
朱丽叶   完成100取款!余额为200
朱丽叶   准备取款!
朱丽叶   完成100取款!余额为100
朱丽叶   准备取款!
朱丽叶   完成100取款!余额为0
余额不足以支付朱丽叶100   的取款,余额为0
余额不足以支付朱丽叶100   的取款,余额为0
余额不足以支付罗密欧100   的取款,余额为0
余额不足以支付罗密欧100   的取款,余额为0
余额不足以支付罗密欧100   的取款,余额为0

分析结果:

双线程总共取款10次,账户总额为500.

取款结果:在多线程访问下,成功取款总额为500,并且其他取款下,正确提示信息。

多线程访问安全保证!

目录
相关文章
|
7天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
25 1
|
5天前
|
Java 关系型数据库 MySQL
【JavaEE“多线程进阶”】——各种“锁”大总结
乐/悲观锁,轻/重量级锁,自旋锁,挂起等待锁,普通互斥锁,读写锁,公不公平锁,可不可重入锁,synchronized加锁三阶段过程,锁消除,锁粗化
|
1月前
|
供应链 安全 NoSQL
PHP 互斥锁:如何确保代码的线程安全?
在多线程和高并发环境中,确保代码段互斥执行至关重要。本文介绍了 PHP 互斥锁库 `wise-locksmith`,它提供多种锁机制(如文件锁、分布式锁等),有效解决线程安全问题,特别适用于电商平台库存管理等场景。通过 Composer 安装后,开发者可以利用该库确保在高并发下数据的一致性和安全性。
39 6
|
1月前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
49 4
|
2月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
25 2
|
1月前
|
数据采集 Java Python
爬取小说资源的Python实践:从单线程到多线程的效率飞跃
本文介绍了一种使用Python从笔趣阁网站爬取小说内容的方法,并通过引入多线程技术大幅提高了下载效率。文章首先概述了环境准备,包括所需安装的库,然后详细描述了爬虫程序的设计与实现过程,包括发送HTTP请求、解析HTML文档、提取章节链接及多线程下载等步骤。最后,强调了性能优化的重要性,并提醒读者遵守相关法律法规。
68 0
|
2月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
60 1
|
2月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
36 3
|
2月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
41 2
|
2月前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
48 1