一、线程同步 = 队列 + 锁
同步就是多个线程同时访问一个资源。
那么如何实现? 队列+锁。
想要访问同一资源的线程排成一个队列,按照排队的顺序访问。访问的时候加上一个锁(参考卫生巾排队+锁门),访问完释放锁。
二、 不安全案例
2.1 不安全的抢票系统
之前我们实现过这个例子。
package Unsafe;
public class RailwayTicketSystem{
public static void main(String[] args) {
BuyTicket buyer = new BuyTicket();
new Thread(buyer,"黑黑").start();
new Thread(buyer,"白白").start();
new Thread(buyer,"黄牛党").start();
}
}
class BuyTicket implements Runnable{
private int ticketNums = 10; //系统里有10张票
//抢票行为
@Override
public void run() {
while(ticketNums>0){
try {
Thread.sleep(100); //模拟延时,放大问题的发生性
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->抢到了第"+ticketNums+"张票");
ticketNums--;
}
}
}
2.2 不安全的银行取钱
场景: 黑土有一张存款为100万的卡,黑土去银行柜台取钱50万,同一时刻,黑土的老婆白云也要通过网上银行从卡里取走100万。
因为取钱是用户各自取各自账户里的钱,不存在多个线程操作同一个对象(所有用户都去抢系统里的票),所以可以用extends Thread。
but why?
package Unsafe;
public class UnsafeBank {
public static void main(String[] args) {
//黑黑的卡里一共有100万
Account 黑土的卡 = new Account("黑土的卡",100);
//黑黑要从卡里取走50万
DrawMoney 黑土 = new DrawMoney(黑土的卡,50);
黑土.start();
//同时,白白也来到了银行,白白要从卡里取走100万
DrawMoney 白云 = new DrawMoney(黑土的卡, 100);
白云.start();
}
}
//银行卡
class Account{
private String name; //持卡人
private int money ; //余额
public Account(String name, int money) {
this.name = name;
this.money = money;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
//银行:模拟取款
class DrawMoney extends Thread{
Account account; //账户
int drawMoney; //要取多少钱
public DrawMoney(Account account,int drawMoney){
this.account = account;
this.drawMoney = drawMoney;
}
//取钱
@Override
public void run() {
if(account.getMoney()-drawMoney<0){
System.out.println("余额已不足,【"+Thread.currentThread().getName()+"】无法取钱");
return;
}
//延时,放大问题的发生
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//余额变动
account.setMoney(account.getMoney() - drawMoney);
System.out.println(Thread.currentThread().getName()+"取走了"+drawMoney);
//输出余额
System.out.println(account.getMoney());
}
}
2.3 不安全的集合
这里以ArraryList为例,我们知道ArraryList的底层是用数组存储的。当多个线程同时执行add方法时,会出现多个线程向数组的同一个位置存放数据的情况。
三、同步机制
由于我们可以用private关键字来保证数据只能被方法访问,所以我们只需要针对类似于getXX()方法提出一套机制,这套机制就是synchronized关键字。它包括两种用法
1. synchronized方法
2. synchronized块