把出现线程安全问题的核心代码给上锁:(每次只能进入一个线程,相当于给线程排队,一个一个进)
synchronized [ˈsɪŋkrənaɪzd] 同步的意思 这个锁没有特别的意思,就是一个flag的标志
作为锁: ()里面写“itheima”也可以,因为是在字符串常量池中,只有一份
上锁快捷键:ctrl + t 选第9个
使用共享资源来定义锁对象, 不要使用唯一的对象来作为锁的对象
实例方法用this代表锁对象(实现共享,是调用就是哪个对象)
静态方法使用 :类名.class(将其锁住) 类名.class 对于所有线程来说都是唯一 一个
package com.itheima.d3_thread_safe;
/**
* 需求: 模拟取钱案例。
*/
public class ThreadDemo {
public static void main(String[] args) {
// 1. 定义线程类,创建一个共享的账户对象
Account acc = new Account("ICBC-111",100000);
// 创建2个线程对象,代表小明和小红同时进来了
// 线程启动
new DrawThread(acc,"小明").start(); // 这是一种匿名写法(不要写对象名,直接创建出来对象后调用其方法)
new DrawThread(acc,"小红").start(); // 这是一种匿名写法(不要写对象名,直接创建出来对象后调用其方法)
// 相当于定义了两家人 (如果定义的锁对象一样,相当于所有的线程都走一样对象的锁,账户没有分开,每个账户公用一把锁)
// 会影响其他无关线程的执行
// 1. 定义线程类,创建一个共享的账户对象
Account acc2 = new Account("ICBC-112",100000);
// 创建2个线程对象,代表小明和小红同时进来了
// 线程启动
new DrawThread(acc2,"小黑").start(); // 这是一种匿名写法(不要写对象名,直接创建出来对象后调用其方法)
new DrawThread(acc2,"小白").start(); // 这是一种匿名写法(不要写对象名,直接创建出来对象后调用其方法)
}
}
package com.itheima.d3_thread_safe;
/**
* 取钱的线程类
*/
public class DrawThread extends Thread{
// 接收处理的账户对象
private Account acc;
// 定义有参和无参构造器
// 无参一般要自己写
public DrawThread(){
}
public DrawThread(Account acc,String name) { // 利用有参构造器为父类Thread线程类取名字
super(name); // 这里的super相当于是父类Thread线程类,直接为该线程类取名
this.acc = acc;
}
// 继承了线程类后:一定要重写run方法
@Override
public void run() {
// 该线程定义: 小红,小明: 取钱
acc.drawMoney(100000); // 该drawMoney方法定义在Account账户类里面
}
}
package com.itheima.d3_thread_safe;
public class Account {
private String cardId;
private double money; // 账户余额
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
// 100个人调用该线程,只有一个线程能占用这个类 -- 其他99个线程在外面等他 (因为调用该方法要用到类名.方法,然而类名被锁住了,所以只有一个人能解锁锁)
public static void run(){ // 由于这个方法是拿账户(Account类)调用,对于所有线程来说都是唯一的方法
// 所以直接把类名锁起来就可以了,
synchronized (Account.class){
}
}
/**
* 在账户类里面定义一个取钱方法drawMoney
* @param money 代表用户取的钱
*/
public void drawMoney(int money) {
// 0. 先获取是谁来取钱,线程的名字就是人嘛
// 使用Thread.currentThread() 获取当前线程对象(谁调用该线程,就获取哪个线程对象),然后使用getNmae方法获取该线程名字
String name = Thread.currentThread().getName();
// 同步代码块: 给会出现线程安全问题的代码块给上锁
// synchronized ("heima") 一样的,都是唯一对象,这个字符串对象都在常量池中,表唯一
// 将字符串对象,改成 this == acc(表示当前对象,当前调用drawMoney方法的对象,这样就不会让账户冲突
// this == acc 共享账户 不会让账户冲突 -- 如果是小明小红账户取钱(调用drawMoney方法),那this代表小明小红(这样就实现了共享)
synchronized (this) { // 直接双引号:因为会在字符串常量池中产生(唯一的一份),满足了锁的唯一性(锁用任意唯一的对象不好)
// 1. 判断账户是否够钱
if (this.money >= money){
// 2. 取钱
System.out.println(name + "来取钱成功,吐出:" + money);
// 3. 更新余额
this.money -= money;
System.out.println(name + "取钱后余额剩余:" + this.money);
}else {
// 余额不足
System.out.println(name + "来取钱,余额不足");
}
}
}
}