承接上篇
一、售票
注释中的解释是星火根据这两个代码做出的解释,感觉有点道理。
运行10次的测试脚本win(注意要在classes目录下运行): for /L %n in (1,1,10) do java -cp . com.wang.juc.sell_ticket.ExerciseSell
方式一:自己写的,虽然解决了线程安全问题,但是相当于没有用多线程
/**
* 在第一个代码(ExerciseSell)中,我们创建了一个TicketWindow对象,并在每个线程中都对其进行了同步操作。
* 也就是说,当一个线程正在执行sell方法时,其他线程必须等待该线程完成才能继续执行。这可以防止多个线程同时修改票的数量,
* 从而避免了竞态条件。然而,这也意味着线程必须等待其他线程,可能会导致性能下降。
*/
public class ExerciseSell {
public static void main(String[] args) {
TicketWindow ticketWindow = new TicketWindow(2000);
List<Thread> list = new ArrayList<>();
// 用来存储买出去多少张票
List<Integer> sellCount = new Vector<>();
for (int i = 0; i < 2000; i++) {
Thread t = new Thread(() -> {
// 分析这里的竞态条件
synchronized (ticketWindow){
int count = ticketWindow.sell(randomAmount());
sellCount.add(count);
}
});
list.add(t);
t.start();
}
list.forEach((t) -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 买出去的票求和
System.out.println("买出去的票求和==>:"+ sellCount.stream().mapToInt(c -> c).sum());
// 剩余票数
// log.debug("剩余票数:{}", ticketWindow.getCount());
System.out.println("剩余票数==>:"+ ticketWindow.getCount());
}
// Random 为线程安全
static Random random = new Random();
// 随机 1~5
public static int randomAmount() {
return random.nextInt(5) + 1;
}
}
class TicketWindow {
private int count;
public TicketWindow(int count) {
this.count = count;
}
public int getCount() {
return count;
}
public int sell(int amount) {
if (this.count >= amount) {
this.count -= amount;
return amount;
} else {
return 0;
}
}
}
方式二:视频中的答案
/**
* 在第二个代码(ExerciseSell2)中,我们没有在每个线程中对TicketWindow对象进行同步操作,
* 而是在TicketWindow类中的sell方法上添加了synchronized关键字。
* 这意味着,无论何时,只有一个线程可以访问sell方法。
* 这样,我们可以避免在多个线程同时访问sell方法时可能出现的竞态条件。
*/
@Slf4j
public class ExerciseSell2 {
public static void main(String[] args) {
TicketWindow2 ticketWindow = new TicketWindow2(2000);
List<Thread> list = new ArrayList<>();
// 用来存储买出去多少张票
List<Integer> sellCount = new Vector<>();
for (int i = 0; i < 2000; i++) {
Thread t = new Thread(() -> {
// 分析这里的竞态条件
int count = ticketWindow.sell(randomAmount());
sellCount.add(count);
});
list.add(t);
t.start();
}
list.forEach((t) -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 买出去的票求和
System.out.println("买出去的票求和==>:"+ sellCount.stream().mapToInt(c -> c).sum());
// 剩余票数
// log.debug("剩余票数:{}", ticketWindow.getCount());
System.out.println("剩余票数==>:"+ ticketWindow.getCount());
}
// Random 为线程安全
static Random random = new Random();
// 随机 1~5
public static int randomAmount() {
return random.nextInt(5) + 1;
}
}
class TicketWindow2 {
private int count;
public TicketWindow2(int count) {
this.count = count;
}
public int getCount() {
return count;
}
public synchronized int sell(int amount) {
if (this.count >= amount) {
this.count -= amount;
return amount;
} else {
return 0;
}
}
}
二、转账
原视频 https://www.bilibili.com/video/BV16J411h7Rd?p=74
问题代码:
@Slf4j
public class ExerciseTransfer {
public static void main(String[] args) throws InterruptedException {
Account a = new Account(1000);
Account b = new Account(1000);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
a.transfer(b, randomAmount());
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
b.transfer(a, randomAmount());
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
// 查看转账2000次后的总金额
log.debug("total:{}", (a.getMoney() + b.getMoney()));
}
// Random 为线程安全
static Random random = new Random();
// 随机 1~100
public static int randomAmount() {
return random.nextInt(100) + 1;
}
}
class Account {
private int money;
public Account(int money) {
this.money = money;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public void transfer(Account target, int amount) {
if (this.money > amount) {
this.setMoney(this.getMoney() - amount);
target.setMoney(target.getMoney() + amount);
}
}
}
解决方案:
问题分析: 首先对于此代码先找出其临界区(出现线程安全的代码),显而易见的该临界区是Account 的transfer方法,找到临界区之后 再找线程的共享变量,其共享变量为money,但是其money有两个地方改变,一个是this.setMoney,,一个是target.setMoney ,this中money和target中的money不是一样对象,所以如果使用
public synchronized void transfer(Account target, int amount) {
if (this.money > amount) {
this.setMoney(this.getMoney() - amount);
target.setMoney(target.getMoney() + amount);
}
}
//相当于
public void transfer(Account target, int amount) {
synchronized (this){
if (this.money > amount) {
this.setMoney(this.getMoney() - amount);
target.setMoney(target.getMoney() + amount);
}
}
}
其只会锁住this实例
,但target实例
无法锁住,所以目前正确的修改方式为
public void transfer(Account target, int amount) {
synchronized (Account.class){
if (this.money > amount) {
this.setMoney(this.getMoney() - amount);
target.setMoney(target.getMoney() + amount);
}
}
}
使用synchronized (Account.class)
锁住其类对象
,在这个类的所有实例中,只有一个线程能够执行被synchronized(Account .class)包围的代码块,其他线程需要等待
。
当然,此种方案效率比较低,其只是在目前文章的基础上作的修改,后续还会优化。