出现问题的场景模拟
Acount类代码
package Thread_safe;
public class Account {
private String CardId;
private double money;
public Account() {
}
public Account(String cardId, double money) {
CardId = cardId;
this.money = money;
}
public String getCardId() {
return CardId;
}
public void setCardId(String cardId) {
CardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public void DrawMoney(Double money) {
String name=Thread.currentThread().getName();
if (this.money>=money){
System.out.println(name+"取钱"+money+"元");
this.money-=money;
System.out.println("还剩"+this.money+"元");
}else {
System.out.println(name+"取钱,余额不足");
}
}
}
DrawThread类代码
public class DrawThread extends Thread{
private Account acc;
public DrawThread(Account acc,String name){
super(name);
this.acc=acc;
}
@Override
public void run() {
acc.DrawMoney(100.0);
}
}
public static void main(String[] args) {
Account acc=new Account("ICBC-123",100);
new DrawThread(acc,"小明").start();
new DrawThread(acc,"小红").start();
}
运行结果
小明取钱100.0元
小红取钱100.0元
还剩-100.0元
还剩0.0元
解决方案
一,线程同步
核心思想:加锁,让线程依次访问共享资源
public void DrawMoney(Double money) {
String name=Thread.currentThread().getName();
synchronized ("heima") {//用"heima"定义锁对象
if (this.money>=money){
System.out.println(name+"取钱"+money+"元");
this.money-=money;
System.out.println("还剩"+this.money+"元");
}else {
System.out.println(name+"取钱,余额不足");
}
}
}
结果
小明取钱100.0元
还剩0.0元
小红取钱,余额不足
小明小红将谁先来谁先得到锁,如果同时来,竞争锁的算法会决定谁得到锁
如果小明抢到锁,在小明执行synchronized 包围的语句时,小红无法进入语句,待小明执行完小红才可以执行
注意:如果用唯一对象定义锁(如"heima")会影响其他无关线程的执行,一家人用一把锁
规范:建议使用共享资源作为锁对象,对实例方法建议使用this作为锁对象,对于静态方法建议用类名.class作为锁对象
public static void run(){
synchronized (Account.class) {
System.out.println("run");
}
}
public void DrawMoney(Double money) {
String name=Thread.currentThread().getName();
synchronized (this) {//用当前的Account对象最为锁对象
if (this.money>=money){
System.out.println(name+"取钱"+money+"元");
this.money-=money;
System.out.println("还剩"+this.money+"元");
}else {
System.out.println(name+"取钱,余额不足");
}
}
}
二,同步方法
在方法返回值前加synchronized
public synchronized void DrawMoney(Double money)
前两种方法对比
第一种范围小更精确,可以都先执行没被锁的语句。
第二种方便,只要加一个synchronized(开发常用),其实比第一种慢不了多少,而且后面还有优化方案。
三,lock锁
public class Account {
private String CardId;
private double money;
//加上final后锁是唯一不可替换的,非常专业
private final Lock lock=new ReentrantLock();
///省略。。。。。
public void DrawMoney(Double money) {
String name=Thread.currentThread().getName();
lock.lock();
try {
if (this.money>=money){
System.out.println(name+"取钱"+money+"元");
this.money-=money;
System.out.println("还剩"+this.money+"元");
}else {
System.out.println(name+"取钱,余额不足");
}
} finally {//防止出现异常,锁不解开
lock.unlock();
}
}
}