多线程的安全问题:每一个执行任务为“进程”,而每一个进程有多个线程,多个线程同时运行,会有意想不到的情况发生。许多在单线程情况下的代码放到多线程环境下容易出现线程安全问题。
例如:通过模拟取钱过程演示线程安全问题。
一般取钱过程:
-
用户输入账户、密码,系统判断用户的账户、密码是否匹配。
-
用户输入取款金额。
-
系统判断账户余额是否大于取款金额。
-
如果余额大于取款金额,则取款成功;如果余额小于取款金额,则取款失败。
在模拟过程中就模拟后面三步,没有模拟账户密码验证过程
如果两者在相隔一秒进行取钱时,其中前面一方出现网络延误,和后面一方同时取钱,那么就会出现钱都有取出,但扣款却有一方的没有扣到的情况。
使用同步代码块:
为了解决线程安全问题,Java的多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块。
同步代码块的语法格式如下:
synchronized (obj) {
//同步代码块
}
obj叫做同步监视器(锁对象),任何线程进入下面同步代码块之前必须先获得对obj的锁;其他线程无法获得锁,也就执行同步代码块。这种做法符合:“加锁-修改-释放锁”的逻辑。锁对象可以是任意对象,
但必须保证是同一对象
任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后该线程会释放对该同步监视器的锁定。
以下就是通过模拟取钱来演示同步代码块:
public static void main(String[] args) {
Account account = new Account(“13138794895s”, 5000);//构造器的三个参数 TakeMoney2 takeMoney1=new TakeMoney2("A", account, 300); TakeMoney2 takeMoney2=new TakeMoney2("B", account, 4830); //通过随机数,随机先启动某个线程 Random random = new Random(); int rendomInt= random.nextInt(100); //随机数 System.out.println(rendomInt); if(rendomInt % 2==0) { takeMoney1.start(); //start() 调用run方法 takeMoney2.start(); }else { takeMoney2.start(); takeMoney1.start(); } //等待子线程的结束 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(account.getNumber() +"账户最终余额:"+account.getMoney());
}
}
class TakeMoney2 extends Thread{
/**- 谁取钱
*/
private String name;
/**
- 账户对象
*/
private Account account;
/**
- 取多少钱
*/
private double takeMoney;
public TakeMoney2(String name,Account account,double takeMoney) {
super();
this.name = name;
this.account = account;
this.takeMoney = takeMoney;
}
@Override
public void run() {
synchronized (account) {
//如果要取的金额 <= 账户的金额
if(takeMoney <= account.getMoney()) {
//模拟取钱成功
System.out.println(this.name + “取钱成功,取出”+takeMoney +“元”);
//线程暂停 10ms 模拟网络传输
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改余额
double money = account.getMoney() - takeMoney;
System.out.println(this.name +“取钱后,计算余额 =”+money);
account.setMoney(money);System.out.println(this.name +"取钱后,账户:"+account.getNumber() +"余额为:"+account.getMoney()); }else { System.out.println(this.name+"取钱失败,原因:"+account.getNumber()+"账户余额不足"); } }
}
} - 谁取钱
首先在主函数中定义两个账户及要取走的金额,然后通过随机数决定哪个账户先取钱,
而后在取钱的方法中给取钱的账户加锁,synchronized(账户名称){代码块}。
从而实现只有一个线程在取钱。