利用多线程模拟3个窗口卖票

第一种方法:继承 Thread 类

 创建窗口类TicketSell

 1 package com.study.thread;
 2 
 3 
 4 public class MyThreadSell {
 5     public static void main(String[] args) {
 6         TicketSell t1 = new TicketSell("A窗口");
 7         TicketSell t2 = new TicketSell("B窗口");
 8         TicketSell t3 = new TicketSell("C窗口");
 9 
10         //启动 3 个窗口进行买票
11         t1.start();
12         t2.start();
13         t3.start();
14 
15     }
16 }
17 
18 class TicketSell extends Thread {
19     //定义一共有50张票,注意声明为static,表示几个窗口共享
20     private static int num = 50;
21 
22     //调用父类构造方法,给线程命名
23     public TicketSell(String name) {
24         super(name);
25     }
26 
27     @Override
28     public void run() {
29         for (int i = 0; i < 50; i++) {
30             if (num > 0) {
31                 try {
32                     sleep(10);
33                 } catch (Exception e) {
34                     e.printStackTrace();
35                 }
36                 System.out.println(this.currentThread().getName() + "卖出一张票,剩余" + (--num) + "张票");
37             }
38         }
39     }
40 
41 }
42 
43 //打印结果
44 A窗口卖出一张票,剩余10张票
45 C窗口卖出一张票,剩余9张票
46 A窗口卖出一张票,剩余9张票
47 B窗口卖出一张票,剩余9张票
48 B窗口卖出一张票,剩余8张票
49 A窗口卖出一张票,剩余8张票
50 C窗口卖出一张票,剩余8张票
51 B窗口卖出一张票,剩余7张票
52 C窗口卖出一张票,剩余7张票
53 A窗口卖出一张票,剩余6张票
54 A窗口卖出一张票,剩余5张票
55 B窗口卖出一张票,剩余4张票
56 C窗口卖出一张票,剩余5张票
57 A窗口卖出一张票,剩余3张票
58 B窗口卖出一张票,剩余2张票
59 C窗口卖出一张票,剩余1张票
60 A窗口卖出一张票,剩余0张票
61 C窗口卖出一张票,剩余-1张票
62 B窗口卖出一张票,剩余-2张票

 结果:这里我们省略了一些,根据电脑配置,结果会随机出现不同,可以发现,有两个或者三个窗口卖的是同一张票,剩余票数还是负数,这在现实情况中是不会发生的

第二种方法:实现 Runnable 接口

 

 1 class TicketSellRunnable implements Runnable{
 2     //定义一共有50张票,注意声明为static,表示几个窗口共享
 3     private static int num = 50;
 4 
 5     //调用父类构造方法,给线程命名
 6 
 7 
 8     @Override
 9     public void run() {
10         for (int i = 0; i < 50; i++) {
11             if (num > 0) {
12                 try {
13                     Thread.sleep(10);
14                 } catch (Exception e) {
15                     e.printStackTrace();
16                 }
17                 System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + (--num) + "张票");
18             }
19         }
20     }
21 }
22 
23 public class MyThreadSell {
24     public static void main(String[] args) {
25 //        TicketSell t1 = new TicketSell("A窗口");
26 //        TicketSell t2 = new TicketSell("B窗口");
27 //        TicketSell t3 = new TicketSell("C窗口");
28 //
29 //        //启动 3 个窗口进行买票
30 //        t1.start();
31 //        t2.start();
32 //        t3.start();
33         TicketSellRunnable t = new TicketSellRunnable();
34         Thread t1 = new Thread(t,"A窗口");
35         Thread t2 = new Thread(t,"B窗口");
36         Thread t3 = new Thread(t,"C窗口");
37 
38                 //启动 3 个窗口进行买票
39         t1.start();
40         t2.start();
41         t3.start();
42 
43     }
44 }
45 
46 //打印结果
47 B窗口卖出一张票,剩余12张票
48 A窗口卖出一张票,剩余11张票
49 B窗口卖出一张票,剩余9张票
50 C窗口卖出一张票,剩余10张票
51 A窗口卖出一张票,剩余8张票
52 B窗口卖出一张票,剩余7张票
53 C窗口卖出一张票,剩余7张票
54 A窗口卖出一张票,剩余6张票
55 C窗口卖出一张票,剩余5张票
56 B窗口卖出一张票,剩余4张票
57 A窗口卖出一张票,剩余3张票
58 C窗口卖出一张票,剩余2张票
59 B窗口卖出一张票,剩余1张票
60 A窗口卖出一张票,剩余0张票
61 B窗口卖出一张票,剩余-1张票
62 C窗口卖出一张票,剩余-2张票

 

 结果分析:这里出现了票数为 负数的情况,这在现实生活中肯定是不存在的,那么为什么会出现这样的情况呢?

 

 

解决办法分析:即我们不能同时让超过两个以上的线程进入到 if(num>0)的代码块中,不然就会出现上述的错误。我们可以通过以下三个办法来解决:

1、使用 同步代码块

2、使用 同步方法

3、使用 锁机制

语法:
synchronized (同步锁) {
    //需要同步操作的代码          
}
 
同步锁:为了保证每个线程都能正常的执行原子操作,Java 线程引进了同步机制;同步锁也叫同步监听对象、同步监听器、互斥锁;
Java程序运行使用的任何对象都可以作为同步监听对象,但是一般我们把当前并发访问的共同资源作为同步监听对象
 
注意:同步锁一定要保证是确定的,不能相对于线程是变化的对象;任何时候,最多允许一个线程拿到同步锁,谁拿到锁谁进入代码块,而其他的线程只能在外面等着

 

 ①、使用同步代码块

 1 class TicketSellRunnable implements Runnable{
 2     //定义一共有50张票,注意声明为static,表示几个窗口共享
 3     private static int num = 50;
 4     //调用父类构造方法,给线程命名
 5     @Override
 6     public void run() {
 7         for (int i = 0; i < 50; i++) {
 8             synchronized (this.getClass()){
 9                 if (num > 0) {
10                     try {
11                         Thread.sleep(10);
12                     } catch (Exception e) {
13                         e.printStackTrace();
14                     }
15                     System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + (--num) + "张票");
16                 }
17             }
18         }
19     }
20 }
21 
22 A窗口卖出一张票,剩余49张票
23 A窗口卖出一张票,剩余48张票
24 A窗口卖出一张票,剩余47张票
25 A窗口卖出一张票,剩余46张票
26 A窗口卖出一张票,剩余45张票
27 A窗口卖出一张票,剩余44张票
28 A窗口卖出一张票,剩余43张票
29 C窗口卖出一张票,剩余42张票
30 C窗口卖出一张票,剩余41张票
31 C窗口卖出一张票,剩余40张票
32 C窗口卖出一张票,剩余39张票
33 C窗口卖出一张票,剩余38张票
34 C窗口卖出一张票,剩余37张票
35 C窗口卖出一张票,剩余36张票
36 C窗口卖出一张票,剩余35张票
37 C窗口卖出一张票,剩余34张票
38 C窗口卖出一张票,剩余33张票
39 C窗口卖出一张票,剩余32张票
40 C窗口卖出一张票,剩余31张票
41 C窗口卖出一张票,剩余30张票
42 C窗口卖出一张票,剩余29张票
43 C窗口卖出一张票,剩余28张票
44 C窗口卖出一张票,剩余27张票
45 C窗口卖出一张票,剩余26张票
46 C窗口卖出一张票,剩余25张票
47 C窗口卖出一张票,剩余24张票
48 C窗口卖出一张票,剩余23张票
49 C窗口卖出一张票,剩余22张票
50 C窗口卖出一张票,剩余21张票
51 C窗口卖出一张票,剩余20张票
52 C窗口卖出一张票,剩余19张票
53 B窗口卖出一张票,剩余18张票
54 B窗口卖出一张票,剩余17张票
55 B窗口卖出一张票,剩余16张票
56 B窗口卖出一张票,剩余15张票
57 B窗口卖出一张票,剩余14张票
58 B窗口卖出一张票,剩余13张票
59 B窗口卖出一张票,剩余12张票
60 B窗口卖出一张票,剩余11张票
61 B窗口卖出一张票,剩余10张票
62 B窗口卖出一张票,剩余9张票
63 B窗口卖出一张票,剩余8张票
64 B窗口卖出一张票,剩余7张票
65 B窗口卖出一张票,剩余6张票
66 B窗口卖出一张票,剩余5张票
67 B窗口卖出一张票,剩余4张票
68 B窗口卖出一张票,剩余3张票
69 B窗口卖出一张票,剩余2张票
70 B窗口卖出一张票,剩余1张票
71 B窗口卖出一张票,剩余0张票

 

从结果看,加了同步代码块后,解决了之前的问题

②、使用 同步方法

语法:即用  synchronized  关键字修饰方法

class TicketSellRunnable implements Runnable{
    //定义一共有50张票,注意声明为static,表示几个窗口共享
    private static int num = 50;
    //调用父类构造方法,给线程命名
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
//            synchronized (this.getClass()){
//                if (num > 0) {
//                    try {
//                        Thread.sleep(10);
//                    } catch (Exception e) {
//                        e.printStackTrace();
//                    }
//                    System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + (--num) + "张票");
//                }
//            }
            sell();
        }
    }
    
    public synchronized void sell(){
        if (num > 0) {
            try {
                Thread.sleep(10);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + (--num) + "张票");
        }
    }
}

注意:不能直接用 synchronized 来修饰 run() 方法,因为如果这样做,那么就会总是第一个线程进入其中,而这个线程执行完所有操作,即卖完所有票了才会出来。

③、使用 锁机制

public interface Lock

主要方法:

 

 常用实现类:

1 public class ReentrantLock
2 extends Object
3 implements Lock, Serializable<br>//一个可重入互斥Lock具有与使用synchronized方法和语句访问的隐式监视锁相同的基本行为和语义,但具有扩展功能。

 

例子:

 1 //创建一个锁对象
 2     Lock l = new ReentrantLock();
 3 
 4 
 5     @Override
 6     public void run() {
 7         for (int i = 0; i < 50; i++) {
 8             //获取锁
 9             l.lock();
10             try {
11                 if (num > 0) {
12                     Thread.sleep(10);
13                     System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + (--num) + "张票");
14                 }
15             } catch (Exception e) {
16                 e.printStackTrace();
17             } finally {
18                 //释放锁
19                 l.unlock();
20             }
21         }
22     }

 

posted on 2020-09-13 17:17  二十二画生的执著  阅读(120)  评论(0编辑  收藏  举报