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

实现Runnable的Ticket类:

线程安全问题_private

 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 }

线程安全问题_private

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

线程安全问题_private

 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 }

线程安全问题_private

输出结果可以看到,三个线程抢占式地将50张车票完全售出:

线程安全问题_多线程_05 View Code

但是在实际应用场景中,我们通常要考虑到因为网络延迟等其他因素造成的购票延迟,这里我们将Ticket稍微进行了改造:

线程安全问题_private

 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 }

线程安全问题_private

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

线程安全问题_private

 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号

线程安全问题_private

产生这种结果的原因:

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

线程安全问题_package_10

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

*是否是多线程环境

*是否有共享数据

*是否有多条语句操作共享数据

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

线程安全问题_private

 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 }

线程安全问题_private

再来运行,结果如下:

线程安全问题_private_13 View Code

可以看到,不再有重复的票出现。当然同步代码块也有它的弊端,当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。