1、什么是线程安全问题
2、用程序模拟线程安全问题
代码说明:
- Account代表账户类
- DrawThread代表线程类
- ThreadTest运行线程类
Account类:
package ThreadSave;
public class Account {
private double money; //余额
private String cardId; //卡号
public Account() {
}
public Account(double money, String cardId) {
this.money = money;
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
//取钱操作
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(name+"来取钱后余额为:"+ this.money);
}else {
System.out.println(name+"来取钱---余额不足");
}
}
}
DrawThread类:
package ThreadSave;
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(100000);
}
}
ThreadTest类:
package ThreadSave;
//目标:模拟线程安全问题
public class ThreadTest {
public static void main(String[] args) {
//1、创建一个账户对象,代表两个人的共享账号
Account acc = new Account(100000,"ICBC-110");
//2、创建2个线程,分别代表小明,小红,再去同一个账户对象取10万元
new DrawThread(acc,"小明").start(); //代表小明
new DrawThread(acc,"小红").start(); //代表小红
}
}
运行结果:
3、线程同步
1、同步代码块
在共享资源上加锁,我们上面案例的共享资源在Account类中,因此我们需要对Account中的代码进行加锁,修改后的代码:
加锁快捷键:选中取钱部分代码----》Ctrl+Alt+T选择synchronized
package ThreadSave;
public class Account {
private double money; //余额
private String cardId; //卡号
public Account() {
}
public Account(double money, String cardId) {
this.money = money;
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
//取钱操作
public void drawMoney(double money) {
//先搞清楚谁在取钱
String name = Thread.currentThread().getName();
//1、判断余额是否足够
//-------------------------------------------------------------------------------------
//加锁
synchronized ("海梦") {
//开始进入取钱逻辑
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 static void test(){
synchronized (Account.class){
}
}
2、同步方法
对方法加锁:
取钱操作
public synchronized void drawMoney(double money)
具体代码:
package ThreadSave;
public class Account {
private double money; //余额
private String cardId; //卡号
public Account() {
}
public Account(double money, String cardId) {
this.money = money;
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
//取钱操作
public synchronized void drawMoney(double money) {
//先搞清楚谁在取钱
String name = Thread.currentThread().getName();
//1、判断余额是否足够
//this正好代表共享资源
//开始进入取钱逻辑
if (this.money>=money){
System.out.println(name+"来取钱"+money+"成功!");
this.money -= money;
System.out.println(name+"来取钱后余额为:"+ this.money);
}else {
System.out.println(name+"来取钱---余额不足");
}
}
}
3、Lock锁
在Account类中创建一个锁对象
然后在特定的位置(共享资源位置)加锁
执行完后再解锁
相关代码:
package ThreadSave;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Account {
private double money; //余额
private String cardId; //卡号
//创建一个锁对象
private final Lock lk = new ReentrantLock();
public Account() {
}
public Account(double money, String cardId) {
this.money = money;
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
//取钱操作
public void drawMoney(double money) {
//先搞清楚谁在取钱
String name = Thread.currentThread().getName();
//加锁
lk.lock();
//1、判断余额是否足够
//开始进入取钱逻辑
if (this.money>=money){
System.out.println(name+"来取钱"+money+"成功!");
this.money -= money;
System.out.println(name+"来取钱后余额为:"+ this.money);
}else {
System.out.println(name+"来取钱---余额不足");
}
//解锁
lk.unlock();
}
}
注意事项:如果在lk.lock()和lk.unlock()中间出现bug,那么就会解锁失败,因此我们应该把中间的代码放在try-catch-finally中
示例代码:
try {
//加锁
lk.lock();
//1、判断余额是否足够
//开始进入取钱逻辑
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 {
//解锁
lk.unlock();
}