线程安全问题

本文楼主主要以用户在售票厅购买车票为背景进行多线程的实现。假设A市到B市的车票共50张,共有3个售票窗口在进行售票,使用多线程来模拟理想状况下的用户购票:git

实现Runnable的Ticket类:github

 1 package com.jon.thread;
 2 
 3 public class TicketSell implements Runnable {
 4     private int tickets = 50;//设置车票数量
 5     @Override
 6     public void run() {
 7         while(true){
 8             if(tickets>0){        
 9                 //输出当前是哪一个线程在出售第几张车票
10                 System.out.println(Thread.currentThread().getName() + "正在售第" + (tickets--) + "张车票");
11             }
12         }
13     }
14 
15 }

简单的售票业务构建好后,咱们用三个线程模拟售票窗口来进行测试:安全

 1 package com.jon.thread;
 2 
 3 public class TicketSellTest {
 4     public static void main(String[] args) {
 5         TicketSell ts = new TicketSell();
 6         Thread td1 = new Thread(ts, "售票窗口1");//设置线程名称以区分哪一个售票窗口
 7         Thread td2 = new Thread(ts, "售票窗口2");
 8         Thread td3 = new Thread(ts, "售票窗口3");
 9         td1.start();
10         td2.start();
11         td3.start();
12     }
13 }

输出结果能够看到,三个线程抢占式地将50张车票彻底售出:网络

 1 售票窗口2正在售第50张车票
 2 售票窗口3正在售第49张车票
 3 售票窗口1正在售第48张车票
 4 售票窗口3正在售第46张车票
 5 售票窗口2正在售第47张车票
 6 售票窗口3正在售第44张车票
 7 售票窗口1正在售第45张车票
 8 售票窗口3正在售第42张车票
 9 售票窗口2正在售第43张车票
10 售票窗口3正在售第40张车票
11 售票窗口1正在售第41张车票
12 售票窗口3正在售第38张车票
13 售票窗口2正在售第39张车票
14 售票窗口2正在售第35张车票
15 售票窗口3正在售第36张车票
16 售票窗口3正在售第33张车票
17 售票窗口3正在售第32张车票
18 售票窗口3正在售第31张车票
19 售票窗口3正在售第30张车票
20 售票窗口3正在售第29张车票
21 售票窗口3正在售第28张车票
22 售票窗口1正在售第37张车票
23 售票窗口3正在售第27张车票
24 售票窗口2正在售第34张车票
25 售票窗口3正在售第25张车票
26 售票窗口1正在售第26张车票
27 售票窗口1正在售第22张车票
28 售票窗口1正在售第21张车票
29 售票窗口1正在售第20张车票
30 售票窗口1正在售第19张车票
31 售票窗口1正在售第18张车票
32 售票窗口1正在售第17张车票
33 售票窗口1正在售第16张车票
34 售票窗口1正在售第15张车票
35 售票窗口1正在售第14张车票
36 售票窗口1正在售第13张车票
37 售票窗口1正在售第12张车票
38 售票窗口1正在售第11张车票
39 售票窗口1正在售第10张车票
40 售票窗口1正在售第9张车票
41 售票窗口1正在售第8张车票
42 售票窗口1正在售第7张车票
43 售票窗口1正在售第6张车票
44 售票窗口1正在售第5张车票
45 售票窗口1正在售第4张车票
46 售票窗口1正在售第3张车票
47 售票窗口1正在售第2张车票
48 售票窗口1正在售第1张车票
49 售票窗口3正在售第23张车票
50 售票窗口2正在售第24张车票
View Code

可是在实际应用场景中,咱们一般要考虑到由于网络延迟等其余因素形成的购票延迟,这里咱们将Ticket稍微进行了改造:多线程

 1 package com.jon.thread;
 2 
 3 public class TicketSell implements Runnable {
 4     private int tickets = 50;//设置车票数量
 5     @Override
 6     public void run() {
 7         while(true){
 8             try {
 9                 Thread.sleep(100);//将线程睡眠100毫秒用来模拟延迟
10             } catch (InterruptedException e) {                
11                 e.printStackTrace();
12             }
13             if(tickets>0){        
14                 //输出当前是哪一个线程在出售第几张车票
15                 System.out.println(Thread.currentThread().getName() + "正在售第" + (tickets--) + "张车票");
16             }
17         }
18     }
19 
20 }

再次运行,能够看到有些售票窗口售出了相同的票,甚至还出现了-一、0 ,很明显出现了线程安全问题:ide

 1 售票窗口1正在售第49张车票
 2 售票窗口2正在售第49张车票
 3 售票窗口3正在售第50张车票
 4 售票窗口2正在售第48张车票
 5 售票窗口1正在售第46张车票
 6 售票窗口3正在售第47张车票
 7 售票窗口2正在售第45张车票
 8 售票窗口1正在售第44张车票//窗口1,3出售了相同的44号车票
 9 售票窗口3正在售第44张车票
10 售票窗口2正在售第43张车票
11 售票窗口1正在售第41张车票
12 售票窗口3正在售第42张车票
13 售票窗口2正在售第40张车票
14 售票窗口3正在售第39张车票
15 售票窗口1正在售第39张车票
16 售票窗口1正在售第38张车票
17 售票窗口2正在售第37张车票
18 售票窗口3正在售第36张车票
19 售票窗口1正在售第35张车票
20 售票窗口3正在售第33张车票
21 售票窗口2正在售第34张车票
22 售票窗口1正在售第32张车票
23 售票窗口3正在售第31张车票
24 售票窗口2正在售第30张车票
25 售票窗口3正在售第29张车票
26 售票窗口1正在售第29张车票
27 售票窗口2正在售第28张车票
28 售票窗口3正在售第27张车票
29 售票窗口1正在售第27张车票
30 售票窗口2正在售第26张车票
31 售票窗口1正在售第25张车票
32 售票窗口3正在售第24张车票
33 售票窗口2正在售第23张车票
34 售票窗口1正在售第22张车票
35 售票窗口3正在售第21张车票
36 售票窗口2正在售第20张车票
37 售票窗口1正在售第19张车票
38 售票窗口3正在售第18张车票
39 售票窗口2正在售第17张车票
40 售票窗口3正在售第16张车票
41 售票窗口1正在售第15张车票
42 售票窗口2正在售第14张车票
43 售票窗口3正在售第13张车票
44 售票窗口1正在售第12张车票
45 售票窗口2正在售第11张车票
46 售票窗口1正在售第10张车票
47 售票窗口3正在售第9张车票
48 售票窗口2正在售第8张车票
49 售票窗口1正在售第7张车票
50 售票窗口3正在售第6张车票
51 售票窗口2正在售第5张车票
52 售票窗口1正在售第4张车票
53 售票窗口2正在售第2张车票
54 售票窗口3正在售第3张车票
55 售票窗口1正在售第0张车票
56 售票窗口3正在售第1张车票
57 售票窗口2正在售第-1张车票//甚至出现了-1号、0号

产生这种结果的缘由:测试

  假设系统在出售“第44张车票”的时候,线程“售票窗口1”获取到了CPU的执行权,流程用下图表示:spa

判断应用程序是否有线程安全的问题不外乎如下几点:  线程

*是不是多线程环境
*是否有共享数据
*是否有多条语句操做共享数据

很明显上面的程序都知足这三点,解决思路:把多个语句操做共享数据的代码给锁起来,让任意时刻只能有一个线程执行便可。楼主这里使用同步代码块改造Ticket类以下:3d

 1 package com.jon.thread;
 2 
 3 public class TicketSell implements Runnable {
 4     private int tickets = 50;
 5     private Object obj = new Object();
 6     @Override
 7     public void run() {
 8         while(true){
 9             synchronized (obj) {
10                 try {
11                     Thread.sleep(100);
12                 } catch (InterruptedException e) {                
13                     e.printStackTrace();
14                 }
15                 if(tickets>0){                
16                     System.out.println(Thread.currentThread().getName() + "正在售第" + (tickets--) + "张车票");
17                 }
18             }            
19         }
20     }
21 
22 }

再来运行,结果以下:

 1 售票窗口3正在售第50张车票
 2 售票窗口3正在售第49张车票
 3 售票窗口3正在售第48张车票
 4 售票窗口1正在售第47张车票
 5 售票窗口1正在售第46张车票
 6 售票窗口1正在售第45张车票
 7 售票窗口2正在售第44张车票
 8 售票窗口2正在售第43张车票
 9 售票窗口2正在售第42张车票
10 售票窗口2正在售第41张车票
11 售票窗口1正在售第40张车票
12 售票窗口1正在售第39张车票
13 售票窗口3正在售第38张车票
14 售票窗口1正在售第37张车票
15 售票窗口2正在售第36张车票
16 售票窗口2正在售第35张车票
17 售票窗口2正在售第34张车票
18 售票窗口2正在售第33张车票
19 售票窗口2正在售第32张车票
20 售票窗口2正在售第31张车票
21 售票窗口1正在售第30张车票
22 售票窗口3正在售第29张车票
23 售票窗口3正在售第28张车票
24 售票窗口3正在售第27张车票
25 售票窗口3正在售第26张车票
26 售票窗口3正在售第25张车票
27 售票窗口3正在售第24张车票
28 售票窗口3正在售第23张车票
29 售票窗口3正在售第22张车票
30 售票窗口3正在售第21张车票
31 售票窗口1正在售第20张车票
32 售票窗口1正在售第19张车票
33 售票窗口1正在售第18张车票
34 售票窗口2正在售第17张车票
35 售票窗口2正在售第16张车票
36 售票窗口2正在售第15张车票
37 售票窗口1正在售第14张车票
38 售票窗口3正在售第13张车票
39 售票窗口3正在售第12张车票
40 售票窗口3正在售第11张车票
41 售票窗口1正在售第10张车票
42 售票窗口1正在售第9张车票
43 售票窗口2正在售第8张车票
44 售票窗口2正在售第7张车票
45 售票窗口2正在售第6张车票
46 售票窗口1正在售第5张车票
47 售票窗口1正在售第4张车票
48 售票窗口1正在售第3张车票
49 售票窗口3正在售第2张车票
50 售票窗口3正在售第1张车票
View Code

能够看到,再也不有重复的票出现。固然同步代码块也有它的弊端,当线程至关多时,由于每一个线程都会去判断同步上的锁,这是很耗费资源的,无形中会下降程序的运行效率。

 有兴趣的小伙伴能够到这里下载文章中用到的代码: https://github.com/LJunChina/JavaResource
相关文章
相关标签/搜索