一、问题
两个用户去银行取钱,登录同一个账户。
分析:
- 需要一个账户类,创建一个账户代表2个人的共享账户
- 定义一个线程类,处理账户对象
- 创建两个线程对象,传入同一个账户对象
- 启动2个线程,去同一个账户对象取钱10万
//账户类
public class Account {
private String CardId;
private double money;
public Account(){}
public void drawMoney(double money){
String name=Thread.currentThread().getName();
if(this.money>=money){
System.out.println(name+"来取钱,吐出"+money+"元。");
this.money-=money;
ystem.out.println(name+"取完余额还剩下:"+this.money);
}else {
System.out.println(name+"来取钱,余额不足!");
}
}
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 class DrawThread extends Thread {
private Account acc;
public DrawThread(String name,Account acc){
super(name);
this.acc=acc;
}
@Override
public void run() {
acc.drawMoney(100);
}
}
public class ThreadDemo {
public static void main(String[] args) {
Account acc=new Account("ICBC-123",100);
Thread t1=new DrawThread("小红",acc);
Thread t2=new DrawThread("小明",acc);
t1.start();
t2.start();
}
}
可见这是不符合显示情况的,故需要实现线程同步!
当多个线程同时访问同一个共享资源且存在修改该资源时出现了此类问题。
二、解决
出现问题原因:多个线程同时执行,发现账户都是够钱的。
如何保证线程安全:让多个线程先后依次访问共享资源
线程同步的核心思想:加锁,把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来。
1 同步代码块
作用
:把出现线程安全问题的核心代码给上锁
。
原理
:
每次只能一个线程进入
,
执行完毕后自动解锁,其他线程才可以进来执行。
![](https://img-blog.csdnimg.cn/3164cb86db7548b5b99acdd4645987be.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6IOh6YC45p2w54ix5a2m5Lmg,size_18,color_FFFFFF,t_70,g_se,x_16)
锁对象要求:锁对象只要对于当前同时执行的线程来说时同一个对象即可
public void drawMoney(double money){
String name=Thread.currentThread().getName();
synchronized ("yijie") {
if(this.money>=money){
System.out.println(name+"来取钱,吐出"+money+"元。");
this.money-=money;
System.out.println(name+"取完余额还剩下:"+this.money);
}else {
System.out.println(name+"来取钱,余额不足!");
}
}
}
锁对象用任意唯一对象即可,但是不好,会影响其他无关线程的执行。在本例中会导致其他账户来取钱时也会被锁住!
规范要求:
- 规范上:建议使用共享资源作为锁对象。
- 对于实例方法建议使用this作为锁对象。
- 对于静态方法建议使用字节码(类名.class)对象作为锁对象。
public void drawMoney(double money){
String name=Thread.currentThread().getName();
synchronized (this) {
if(this.money>=money){
System.out.println(name+"来取钱,吐出"+money+"元。");
this.money-=money;
System.out.println(name+"取完余额还剩下:"+this.money);
}else {
System.out.println(name+"来取钱,余额不足!");
}
}
}
2 同步方法
作用
:把出现线程安全问题的核心
方法
给上锁
。
原理
:
每次只能一个线程进入
,
执行完毕以后自动解锁,其他线程才可以进来执行。
public synchronized void drawMoney(double money){
String name=Thread.currentThread().getName();
if(this.money>=money){
System.out.println(name+"来取钱,吐出"+money+"元。");
this.money-=money;
System.out.println(name+"取完余额还剩下:"+this.money);
}else {
System.out.println(name+"来取钱,余额不足!");
}
}
底层原理:
- 同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
- 如果方法是实例方法:同步方法默认用this作为的锁对象。但是代码要高度面向对象!
- 如果方法是静态方法:同步方法默认用类名.class作为的锁对象。
3 Lock锁
- 为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,更加灵活、方便。
- Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。
- Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来构建Lock锁对象。
private String CardId;
private double money;
//final修饰后,锁对象是唯一和不可替换的,非常专业
private final Lock lock=new ReentrantLock();
public Account(){}
public void drawMoney(double money){
String name=Thread.currentThread().getName();
lock.lock();//上锁
if(this.money>=money){
System.out.println(name+"来取钱,吐出"+money+"元。");
this.money-=money;
System.out.println(name+"取完余额还剩下:"+this.money);
}else {
System.out.println(name+"来取钱,余额不足!");
}
lock.unlock();//解锁,最好放到finally里,防止出现异常导致永远不解锁
}