多个线程同时操作同一个共享资源的时候,可能会出现业务安全问题
取钱的线程安全问题场景:
两个人他们有一个共同的账户,余额是10万元,如果两个人同时来取钱,并且2人各自都在取钱10万元,可能会出现什么问题?
(1)线程安全问题出现的原因:
线程同步就是解决线程安全问题的方案
(1)线程同步的思想:让多个线程实现先后依次访问共享资源,这样就解决了安全问题
(2)线程同步的常见方案
(3)线程同步方式
方式一:同步代码块
①作用:把访问共享资源的核心代码给上锁,以此保证线程的安全
②原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行
③同步锁的注意事项:对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug
④锁对象不能随便选择一个唯一的对象,会影响其他无关线程的执行,例如String字符串
⑤锁对象的使用规范
public class Demo {
public static void main(String[] args) {
//3、创建账户对象(共享数据)
Account acc = new Account("9527", 100000);
//4、两个线程同时取款(多个线程操作共享数据,出现线程安全问题)
new MyThread(acc, "小明").start();
new MyThread(acc, "小红").start();
}
}
//2、线程类,封装取款的代码
class MyThread extends Thread {
private Account acc;
public MyThread(Account acc, String name) {
super(name);
this.acc = acc;
}
@Override
public void run() {
//取钱时,要传递取款金额
acc.drawMoney(100000);
}
}
//1、账户类,包含取款功能
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;
}
//取款功能,参数money为取款金额
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 + "取钱失败,余额不足");
}
}
}
//对于静态方法建议使用字节码(类名.class)对象作为锁对象
public static void test(){
synchronized (Account.class){
}
}
}
方式二:同步方法
①作用:把访问共享资源的核心方法上锁,以此保证线程安全
②原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行
③同步方法的底层原理
//线程类和测试类同上
//1、账户类,包含取款功能
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;
}
//取款功能,参数money为取款金额
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 + "取钱失败,余额不足");
}
}
//静态方法
public synchronized static void test(){
}
}
④同步代码块和同步方法的区别
同步代码块:锁对象可以指定,锁的范围可以指定,性能高且灵活
同步方法:锁对象不能指定,锁的是方法体,阅读性更高
方式三:Lock锁
①Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大
②Lock是接口,不能直接被实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象
public ReentrantLock():获得Lock锁的实现类对象
③Lock的常用方法
④Lock锁使用规范
public class Demo {
public static void main(String[] args) {
//3、创建账户对象(共享数据)
Account acc = new Account("9527", 100000);
//4、两个线程同时取款(多个线程操作共享数据,出现线程安全问题)
new MyThread(acc, "小明").start();
new MyThread(acc, "小红").start();
}
}
//2、线程类,封装取款的代码
class MyThread extends Thread {
private Account acc;
public MyThread(Account acc, String name) {
super(name);
this.acc = acc;
}
@Override
public void run() {
//取钱时,要传递取款金额
acc.drawMoney(100000);
}
}
//1、账户类,包含取款功能
class Account {
//卡号
private String cardID;
//余额
private double money;
//规范1、锁对象创建在成员位置,使用final修饰
private final ReentrantLock lock = new ReentrantLock();
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;
}
//取款功能,参数money为取款金额
public void drawMoney(double money) {
String name = Thread.currentThread().getName();
try {
//上锁
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 + "取钱失败,余额不足");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
}
}