多线程学习笔记04线程同步
并发
同一个对象被多个线程同时操作,比如上万人同时抢100张票
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
并发实际上就是同一个对象被多个线程同时操作,这里强调三个点:
1、同一个对象(如果每个人操作自己的银行卡显然不会发生并发)
2、多个线程(如果这个银行卡只有你一个人用显然也不会发生问题)
3、同时操作(大家都在操作这个卡的时候,就会发生并发问题)。
并发三要素:同一个对象、多线程、同时。
一旦发生并发就会产生数据不准确的问题,之前的账户余额不对、12306的票出现了负数或者同一个位置被多个人买到,还有容器内的数据不对,这些都称为线程不安全。
保证线程安全可以通过“队列”和“锁”来完成。
线程同步
线程同步实现的两个条件:①等待池形成队列;②资源上锁。
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized ,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。锁机制存在的问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起;
- 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
- 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题。
三大不安全案例
案例一,买票:
//线程不安全
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
//三个人买票
new Thread(station, "我").start();
new Thread(station, "你").start();
new Thread(station, "黄牛党").start();
}
}
class BuyTicket implements Runnable {
//票
private int ticketNums = 10;
boolean flag = true;//外部停止方式
@Override
public void run() {
//买票
while (flag) {
buy();
}
}
private void buy() {
//判断是否有票
if (ticketNums <= 0) {
flag = false;
return;
}
//模拟延时
try {
Thread.sleep(80);
} catch (InterruptedException e) {
e.printStackTrace();
}
//买票
System.out.println(Thread.currentThread().getName() + "拿到了" + ticketNums--);
}
}
运行结果:
存在0和-1,线程不安全!
0和-1是怎么来的?
每个线程都在自己的工作内存交互,内存控制不当会造成数据不一致
当余票数显示为1时,我、你、黄牛党都看到余数为1,都进行买票操作,当其中一个人成功拿到1时,余票为0,0被第二个人拿到后,余票为-1且被第三个人拿到,这就是原因所在。由此可见,如果不让三者排队,则线程会变得非常不安全。
案例二,银行取钱:
//不安全的取钱
public class UnsafeBank {
public static void main(String[] args) {
//账户
Account account = new Account(100, "结婚基金");
Drawing you = new Drawing(account, 50, "你");//你要取50万
Drawing girlfriend = new Drawing(account, 100, "girlfriend");//你你女朋友要全部取走
//你们两个都要取钱
you.start();
girlfriend.start();
}
}
//账户
class Account {
int money;//余额
String name;//卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行,模拟取款
class Drawing extends Thread {
Account account;//账户
//取了多少钱
int drawingMoney;
//手中现有多少钱
int nowMoney;
public Drawing(Account account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱
@Override
public void run() {
//判断有没有钱
if (account.money - drawingMoney < 0) {
System.out