Android(java)学习笔记67:多线程程序练习

需求: 某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。

两种方式实现

A:继承Thread类

B:实现Runnable接

 

1.首先我们利用方式A去实现:

 1 package cn.itcast_06;
 2 
 3 public class SellTicket extends Thread {
 4 
 5     // 定义100张票
 6     // private int tickets = 100;这时候仍然是创建的3个对象都有tickets = 100的成员变量,这样还是不行的
 7     // 为了让多个线程对象共享这100张票,我们其实应该用静态修饰
 8     private static int tickets = 100;
 9 
10     @Override
11     public void run() {
12         // 定义100张票
13         // 每个线程进来都会走这里,这样的话,每个线程对象相当于买的是自己的那100张票,这不合理,所以应该定义到外面
14         // int tickets = 100;
15 
16         // 是为了模拟一直有票
17         while (true) {
18             if (tickets > 0) {
19                 System.out.println(getName() + "正在出售第" + (tickets--) + "张票");
20             }
21         }
22     }
23 }

 

 1 package cn.itcast_06;
 2 
 3 /*
 4  * 某电影院目前正在上映贺岁大片(红高粱,少林寺传奇藏经阁),共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
 5  * 继承Thread类来实现。
 6  */
 7 public class SellTicketDemo {
 8     public static void main(String[] args) {
 9         // 创建三个线程对象
10         SellTicket st1 = new SellTicket();
11         SellTicket st2 = new SellTicket();
12         SellTicket st3 = new SellTicket();
13 
14         // 给线程对象起名字
15         st1.setName("窗口1");
16         st2.setName("窗口2");
17         st3.setName("窗口3");
18 
19         // 启动线程
20         st1.start();
21         st2.start();
22         st3.start();
23     }
24 }

 

 

2.我们利用Runnable接口去实现:

 1 package cn.itcast_07;
 2 
 3 public class SellTicket implements Runnable {
 4     // 定义100张票
 5     private int tickets = 100;//这里没有加static修饰,因为下面我们只创建一个对象,通过创建3个线程来享用这一个对象资源
 6 
 7     @Override
 8     public void run() {
 9         while (true) {
10             if (tickets > 0) {
11                 System.out.println(Thread.currentThread().getName() + "正在出售第"
12                         + (tickets--) + "张票");
13             }
14         }
15     }
16 }

 

 1 package cn.itcast_07;
 2 
 3 /*
 4  * 实现Runnable接口的方式实现
 5  */
 6 public class SellTicketDemo {
 7     public static void main(String[] args) {
 8         // 创建资源对象
 9         SellTicket st = new SellTicket();
10 
11         // 创建三个线程对象
12         Thread t1 = new Thread(st, "窗口1");
13         Thread t2 = new Thread(st, "窗口2");
14         Thread t3 = new Thread(st, "窗口3");
15 
16         // 启动线程
17         t1.start();
18         t2.start();
19         t3.start();
20     }
21 }

 

关于电影院卖票程序的思考:

我们前面讲解过电影院售票程序,从表面上看不出什么问题,但是在真实生活中,售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟。

 

改实现接口方式的卖票程序

->每次卖票延迟100毫秒

 

但是还是出现问题了:

(1).相同的票出现多次

CPU的一次操作必须是原子性的

技术分享

(2).还出现了负数的票

随机性和延迟导致的

技术分享

 

我将分析注解到源码上,这样看起来直观一点:

 1 package cn.itcast_08;
 2 
 3 public class SellTicket implements Runnable {
 4     // 定义100张票
 5     private int tickets = 100;
 6 
 7 //    @Override
 8 //    public void run() {
 9 //        while (true) {
10 //            // t1,t2,t3三个线程
11 //            // 这一次的tickets = 100;
12 //            if (tickets > 0) {
13 //                // 为了模拟更真实的场景,我们稍作休息
14 //                try {
15 //                    Thread.sleep(100); // t1就稍作休息,t2就稍作休息
16 //                } catch (InterruptedException e) {
17 //                    e.printStackTrace();
18 //                }
19 //
20 //                System.out.println(Thread.currentThread().getName() + "正在出售第"
21 //                        + (tickets--) + "张票");
22 //                // 理想状态:
23 //                // 窗口1正在出售第100张票
24 //                // 窗口2正在出售第99张票
25 //                // 但是呢?
26 //                // CPU的每一次执行必须是一个原子性(最简单基本的)的操作。
27 //                // 先记录以前的值
28 //                // 接着把ticket--
29 //                // 然后输出以前的值(t2来了)
30 //                // ticket的值就变成了99
31 //                // 窗口1正在出售第100张票
32 //                // 窗口2正在出售第100张票
33 //
34 //            }
35 //        }
36 //    }
37     
38     @Override
39     public void run() {
40         while (true) {
41             // t1,t2,t3三个线程
42             // 这一次的tickets = 1;
43             if (tickets > 0) {
44                 // 为了模拟更真实的场景,我们稍作休息
45                 try {
46                     Thread.sleep(100); //t1进来了并休息,t2进来了并休息,t3进来了并休息,
47                 } catch (InterruptedException e) {
48                     e.printStackTrace();
49                 }
50 
51                 System.out.println(Thread.currentThread().getName() + "正在出售第"
52                         + (tickets--) + "张票");
53                 //窗口1正在出售第1张票,tickets=0
54                 //窗口2正在出售第0张票,tickets=-1
55                 //窗口3正在出售第-1张票,tickets=-2
56             }
57         }
58     }
59 }

 

 1 package cn.itcast_08;
 2 
 3 /*
 4  * 实现Runnable接口的方式实现
 5  * 
 6  * 通过加入延迟后,就产生了连个问题:
 7  * A:相同的票卖了多次
 8  *         CPU的一次操作必须是原子性的
 9  * B:出现了负数票
10  *         随机性和延迟导致的
11  */
12 public class SellTicketDemo {
13     public static void main(String[] args) {
14         // 创建资源对象
15         SellTicket st = new SellTicket();
16 
17         // 创建三个线程对象
18         Thread t1 = new Thread(st, "窗口1");
19         Thread t2 = new Thread(st, "窗口2");
20         Thread t3 = new Thread(st, "窗口3");
21 
22         // 启动线程
23         t1.start();
24         t2.start();
25         t3.start();
26     }
27 }

 

 

注意 :线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。

 

上面我们已经说过我们写的程序是有问题的,接下来我们就要解决这种多线程带来的问题:

如何解决线程安全问题呢?

要想解决问题,就要知道哪些原因会导致出问题:(而且这些原因也是以后我们判断一个程序是否会有线程安全问题的标准)

 A:是否是多线程环境

 B:是否有共享数据

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

我们来回想一下我们的程序有没有上面的问题呢?
 A:是否是多线程环境  
 B:是否有共享数据    
 C:是否有多条语句操作共享数据  

 由此可见我们的程序出现问题是正常的,因为它满足出问题的条件。
接下来才是我们要想想如何解决问题呢?
 A和B的问题我们改变不了,我们只能想办法去把C改变一下
 

思想:
把多条语句操作共享数据的代码给包成一个整体,让某个线程在执行的时候,别人不能来执行。
问题是我们不知道怎么包啊?其实我也不知道,但是Java给我们提供了:同步机制。

 同步代码块格式:
synchronized(对象){
 需要同步的代码;
 }

A:对象是什么呢?
 我们可以随便创建一个对象试试。
B:需要同步的代码是哪些呢?
 把多条语句操作共享数据的代码的部分给包起来

 注意:
 同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
 多个线程必须是同一把锁。

 

 1 package cn.itcast_09;
 2 
 3 public class SellTicket implements Runnable {
 4     // 定义100张票
 5     private int tickets = 100;
 6     //创建锁对象
 7     private Object obj = new Object();
 8 
 9 //    @Override
10 //    public void run() {
11 //        while (true) {
12 //            synchronized(new Object()){//这里new Object()表示是新建对象,这里三个对象实际上就是新建三个锁,这样是不可以,需要一把锁
13 //                if (tickets > 0) {
14 //                    try {
15 //                        Thread.sleep(100); 
16 //                    } catch (InterruptedException e) {
17 //                        e.printStackTrace();
18 //                    }
19 //                    System.out.println(Thread.currentThread().getName() + "正在出售第"
20 //                            + (tickets--) + "张票");
21 //                }
22 //            }
23 //        }
24 //    }
25     
26     @Override
27     public void run() {
28         while (true) {
29             synchronized (obj) {//前面已经定义对象的"锁”成员变量,它是表示每个对象的共有资源,这里表示就是3个对象共有的一把锁30                 if (tickets > 0) {
31                     try {
32                         Thread.sleep(100);
33                     } catch (InterruptedException e) {
34                         e.printStackTrace();
35                     }
36                     System.out.println(Thread.currentThread().getName()
37                             + "正在出售第" + (tickets--) + "张票");
38                 }
39             }
40         }
41     }
42 }
 1 public class SellTicketDemo {
 2     public static void main(String[] args) {
 3         // 创建资源对象
 4         SellTicket st = new SellTicket();
 5 
 6         // 创建三个线程对象
 7         Thread t1 = new Thread(st, "窗口1");
 8         Thread t2 = new Thread(st, "窗口2");
 9         Thread t3 = new Thread(st, "窗口3");
10 
11         // 启动线程
12         t1.start();
13         t2.start();
14         t3.start();
15     }
16 }

 

下面关于以上代码的解释:

 1 package cn.itcast_10;
 2 
 3 public class SellTicket implements Runnable {
 4 
 5     // 定义100张票
 6     private int tickets = 100;
 7 
 8     // 定义同一把锁
 9     private Object obj = new Object();
10 
11     @Override
12     public void run() {
13         while (true) {
14             // t1,t2,t3都能走到这里
15             // 假设t1抢到CPU的执行权,t1就要进来
16             // 假设t2抢到CPU的执行权,t2就要进来,发现门是关着的,进不去。所以就等着。
17             // 门(开,关)
18             synchronized (obj) { // 发现这里的代码将来是会被锁上的,所以t1进来后,就锁了。(关)
19                 if (tickets > 0) {
20                     try {
21                         Thread.sleep(100); // t1就睡眠了
22                     } catch (InterruptedException e) {
23                         e.printStackTrace();
24                     }
25                     System.out.println(Thread.currentThread().getName()
26                             + "正在出售第" + (tickets--) + "张票 ");
27                     //窗口1正在出售第100张票
28                 }
29             } //t1就出来可,然后就开门。(开)
30         }
31     }
32 }

 

 1 package cn.itcast_10;
 2 
 3 /*
 4  * 举例:
 5  *         火车上厕所。
 6  * 
 7  * 同步的特点:
 8  *         前提:
 9  *             多个线程
10  *        解决问题的时候要注意:
11  *            多个线程使用的是同一个锁对象
12  * 同步的好处 
13  *        同步的出现解决了多线程的安全问题。
14  * 同步的弊端
15  *        当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
16  */
17 public class SellTicketDemo {
18     public static void main(String[] args) {
19         // 创建资源对象
20         SellTicket st = new SellTicket();
21 
22         // 创建三个线程对象
23         Thread t1 = new Thread(st, "窗口1");
24         Thread t2 = new Thread(st, "窗口2");
25         Thread t3 = new Thread(st, "窗口3");
26 
27         // 启动线程
28         t1.start();
29         t2.start();
30         t3.start();
31     }
32 }

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。