拿近期抢茅台的例子来说吧,天猫超市今天一共有100瓶茅台,但是有1万人抢购,假如我们把每个人的抢购当作一个线程来说,那么咱们实现一下抢茅台的代码:当然,下面的实现是不考虑线程的安全问题的。
首先先抽象一下抢茅台:
定义一个名字为Windows1的类,让所有用户都通过这个窗口去抢:
public class Windows1 implements Runnable {
//初始化的时候给茅台定义100瓶的常量,不再改变
private int sum = 100;
public void run() {
System.out.println("开始抢购茅台");
while(true){
if (sum>0){
//打印抢到的信息
System.out.println(Thread.currentThread().getName()+": 抢购了一瓶剩下:"+sum+"瓶");
//每抢到一瓶就剪减去一瓶
sum--;
}else{
//没有了就跳出循环
break;
}
}
}
}
由于是实现了Runnable的接口创建的线程,所以在main函数中我们这样去处理:
public class MainStart {
public static void main(String[] args) {
//创建实体对象
Windows1 windows1 = new Windows1();
//实体对象当作参数传给Thread构造方法
//我们当前先设置这三个人抢,当然你可以写100行,假设是100个人抢
Thread thread = new Thread(windows1);
Thread thread2 = new Thread(windows1);
Thread thread3 = new Thread(windows1);
//开始抢的动作,线程的激活
thread.start();
thread2.start();
thread3.start();
}
}
这样我们创建了三个窗口,也就是三个线程,让他们都去抢茅台,但是运行起来看一下结果:
一共100瓶,第100瓶被2个人抢走了,这两个人都抢到了编码为100号的茅台,最后这酒给谁那?这时候就要考虑线程的安全问题:
解决线程的安全问题(同步代码块)
这里要引入一个关键字:synchronized
使用结构是这样的:
加这个锁的让代码变为这样的执行流程:
每当有线程A进来要执行同步代码体的时候,就要去判断同步代码体的代码是不是有其他线程在执行,如果有其他线程在执行的话,线程A需要等执行的线程结束,才有可能轮到线程A执行,为什么是有可能,因为线程的执行需要CPU去分配,具体到谁是不确定的。
特别解释一下锁:任何类的对象都可以充当锁去使用,但是多个线程必须用同一把锁,也就是同一个对象,也就是只能让这个对象new一次。
synchronized (锁){
需要同步代码体,这里要理解,为了解决100瓶卖2次的问题,我们要把目标卖出的逻辑,也就是这100瓶的变换的代码当作同步代码体来处理。
}
用线程的方式实现抢100瓶茅台
public class Windows1 implements Runnable {
//初始化的时候给茅台定义100瓶的常量,不再改变
private int sum = 100;
Object obj = new Object();
public void run() {
System.out.println("开始抢购茅台");
while (true) {
synchronized (obj) {
if (sum > 0) {
//打印抢到的信息
System.out.println(Thread.currentThread().getName() + ": 抢购了一瓶剩下:" + sum + "瓶");
//每抢到一瓶就剪减去一瓶
sum--;
} else {
//没有了就跳出循环
break;
}
}
}
}
}
mian函数和上面是一样的,同步的结果如下所示:
如果锁用的不是同一个对象的情况
所以要保证多线程的锁用的是一个对象。