接下来将通过一个案例,演示线程的安全问题:
电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “唐人街探案3”,本次电影的座位共100个(本场电影只能卖100张票)。
我们来模拟电影院的售票窗口,实现多个窗口同时卖 “葫芦娃大战奥特曼”这场电影票(多个窗口一起卖这100张票)需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟。
模拟票:
public class MyRunnable implements Runnable {
int tickets = 100;// 4个窗口共同卖的票 共享变量
@Override
public void run() {
// 实现卖票的操作
// 死循环卖票
while (true){
// 当票卖完了,就结束
if (tickets < 1){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":正在出售第"+tickets+"张票");
tickets--;
}
}
}
测试类:
public class Test {
public static void main(String[] args) {
/*
多行代码的问题:
通过案例演示该问题: 电影院4个窗口卖票,卖的是同一份票,这份票总共有100张票
分析:
1. 电影院4个窗口 相当于 4条线程
2. 电影院4个窗口 卖票的操作是一样 相当于每条线程的任务是一样的
*/
// 电影院4个窗口 去卖票
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr,"窗口1");
Thread t2 = new Thread(mr,"窗口2");
Thread t3 = new Thread(mr,"窗口3");
Thread t4 = new Thread(mr,"窗口4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}![在这里插入图片描述](https://img-blog.csdnimg.cn/20200722214305778.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTQ5Njk4OA==,size_16,color_FFFFFF,t_70)
程序执行后,结果会出现的问题
发现程序出现了两个问题:
- 相同的票数,比如100这张票被卖了四回。
- 不存在的票,比如0票与-1票,-2票,是不存在的。
这种问题,几个窗口(线程)票数不同步了,这种问题称为线程不安全。
问题分析:
解决方案:
- 同步代码块:
synchronized
关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:
synchronized(同步锁){
需要同步操作的代码
}
同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
- 锁对象 可以是任意类型。
- 多个线程对象 要使用同一把锁。
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。
使用同步代码块解决代码:
public class MyRunnable implements Runnable {
int tickets = 100;// 4个窗口共同卖的票 共享变量
@Override
public void run() {
// 实现卖票的操作
// 死循环卖票
while (true) {
// 当票卖完了,就结束
// 加锁
synchronized (this) {// mr钥匙
if (tickets < 1) {
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":正在出售第" + tickets + "张票");
tickets--;
}
// 释放锁
}
}
}
public class Test {
//static Object lock = new Object();
public static void main(String[] args) {
// 电影院4个窗口 去卖票
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr,"窗口1");
Thread t2 = new Thread(mr,"窗口2");
Thread t3 = new Thread(mr,"窗口3");
Thread t4 = new Thread(mr,"窗口4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
当使用了同步代码块后,上述的线程的安全问题,解决了。