示例1:卖票与买票
@Slf4j(topic = "c.CASE1")
public class CASE1 {
//随机数
public static Random random = new Random();
public static int getRandom(){
return random.nextInt(5) + 1;
}
public static void main(String[] args) {
//开始有2000张票
TicketWindow ticketWindow = new TicketWindow(2000);
List<Thread> list = new ArrayList<>();
//用来存储卖出去多少张票,//竞态条件
//List<Integer> sellCount = new ArrayList<>();
List<Integer> sellCount = new Vector<>();
//2000票分2000次卖出去
for (int i = 0; i < 2000; i++) {
Thread t = new Thread(()->{
//竞态态条件
int sell = ticketWindow.sell(getRandom());
sellCount.add(sell);
});
list.add(t);
t.start();
}
//使得每个线程运行结束
list.forEach((t)->{
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
//统计最终结果
log.debug("卖出去:" + sellCount.stream().mapToInt(a->a).sum());
log.debug("剩余票数:" + ticketWindow.getCount());
}
}
class TicketWindow{
private int count;
public TicketWindow(int count) {
this.count = count;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
//卖出去多少张表,如果返回0则表示没卖出去票
public int sell(int amount){
if(amount < count){
count = count - amount;
return amount;
}else{
return 0;
}
}
}
请判断该例子是否存在线程安全?
运行输出结果:
答:存在!
分析:判断是否存在线程安全步骤
-
寻找临界区 :
int sell = ticketWindow.sell(getRandom()); sellCount.add(sell);
-
寻找共享变量
两个共享变量count,sellCount,存在线程安全的只有count变量,因为sellCount的add方法是属于线程安全的,如果换成ArrayList就会出现线程安全问题,count变量属于ticketWindow对象。
解决方法:
可以给共享变量上锁,使用关键字synchronized
,可以将锁加在sell方法上
public synchronized int sell(int amount){
if(amount < count){
count = count - amount;
return amount;
}else{
return 0;
}
}
等价于
public int sell(int amount){
synchronized(this){
if(amount < count){
count = count - amount;
return amount;
}else{
return 0;
}
}
}
示例2:ab互相转账
@Slf4j(topic = "c.CASE2")
public class CASE2 {
private static Random random = new Random();
//返回1-100随机数
public static int getRandom(){
return random.nextInt(100) + 1;
}
public static void main(String[] args) throws InterruptedException {
//账户a
Account a = new Account(1000);
//账户b
Account b = new Account(1000);
Thread t1 = new Thread(()->{
for (int i = 0; i < 1000; i++) {
a.transfer(b,getRandom());
}
});
Thread t2 = new Thread(()->{
for (int i = 0; i < 1000; i++) {
b.transfer(a,getRandom());
}
});
//分别启动t1 t2
t1.start();
t2.start();
//等待t1 t2跑完
t1.join();
t2.join();
log.debug("账户a :" + a.getMoney() );
log.debug("账户b :" + b.getMoney());
log.debug("total:{}",(a.getMoney() +b.getMoney()));
}
}
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);
}
}
}
运行结果可以看出来,存在线程安全问题!
分析:
-
临界区
public void transfer(Account target,int amount){ //有钱可转 synchronized (Account.class){ if(this.money > amount){ this.setMoney(this.getMoney() - amount); target.setMoney(target.getMoney() + amount); } } }
-
共享变量
与上一个例子不同的是,该临界区存在的共享变量有两个,分别是
this.money
和target.money
,所以如果在transfer上面加上锁,还能不能锁得住,能 不能解决线程安全问题呢?public synchronized void transfer(Account target,int amount){ //有钱可转 if(this.money > amount){ this.setMoney(this.getMoney() - amount); target.setMoney(target.getMoney() + amount); } }
并不能!
原因是:此时的synchronized只能锁定住当前的对象的共享变量,也就是说谁调用transfer就会锁住谁的money属性,因此并不能解决线程安全问题,那么该如果才能同时锁住两个对象呢?
解决方法:扩大锁的范围,从锁住对象扩大至锁住整个类,因为无论是this.money
和target.money
它们都属于Account类,因此可以修改临界区的锁范围,如下:
public void transfer(Account target,int amount){
//有钱可转
synchronized (Account.class){
if(this.money > amount){
this.setMoney(this.getMoney() - amount);
target.setMoney(target.getMoney() + amount);
}
}
}
重新运行代码:
此时便达到我们的线程安全目的。