模拟火车票销售系统--线程同步+安全问题(初期 1)

**

利用多线程模拟火车票销售系统,以及给出各个线程暴露出的安全案例分析,尾有完整的模拟售票代码!

**
#首先我们来回顾一下多线程的大概框架内容#
(这里主要讲一些多线程的运行过程,同步代码块,Runnable接口,线程安全,锁的问题)
什么是线程?(当然这只是浅显的介绍,对于深入Android开发的大神们来说,他们能罗列的框架布列远远不止于这些
 多线程的内容概述图

        *线程是程序执行的一条路径, 一个进程中可以包含多条线程
        * 多线程并发执行可以提高程序的效率, 可以同时完成多项工作.....
        那么,多线程用在什么地方呢?
        **举个栗子**

* 红蜘蛛同时共享屏幕给多个电脑
* 迅雷开启多条线程一起下载
* QQ音乐开启多线程同时下载歌曲
* 微信同时和多个人一起视频
* 服务器同时处理多个客户端请求


那么,火车票窗口卖票系统是多线程并行还是并发呢?

  • 在这里,需要考虑一点就是,多线程并行和并发的区别
    • 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
    • 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
    • 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
    • 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
    • 比如我跟两个网友聊天,左手操作一个电脑跟甲聊,同时右手用另一台电脑跟乙聊天,这就叫并行。
    • 如果用一台电脑我先给甲发个消息,然后立刻再给乙发消息,然后再跟甲聊,再跟乙聊。这就叫并发。
    • 比如我跟两个网友聊天,左手操作一个电脑跟甲聊,同时右手用另一台电脑跟乙聊天,这就叫并行。
    • 如果用一台电脑我先给甲发个消息,然后立刻再给乙发消息,然后再跟甲聊,再跟乙聊。这就叫并发。

到这里呢,我们应该结合之前Java基础知识点,来大概了解Java程序运行的原理.
Java程序运行原理
* Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。


这里可能会有小伙伴疑惑,jvm的启动会是多线程吗?
问得好………╰( ̄▽ ̄)╭
jvm启动至少了垃圾回收线程和主线程,所以你的疑问是毋庸置疑的

多线程的构造方法
成员方法

多线程的两种实现方式
实现Runnable的原理
同步代码块
这里写图片描述

多线程程序实现方式1

步骤:
1. 定义类继承Thread
2. 重写run方法
3. 把线程要做的事写在run()方法
4. 创建线程对象,调用start()方法
5. 开启新线程,内部会自动执行run方法

/**
Thread匿名对象实现代码,并设置,获取名字
*/
    public static void main(String[] args) {
        new Thread(){
            public void run(){
                this.setName("线程1");        //设置线程的名字
                System.out.println(this.getName() + "---->线程1");
            }

        }.start();
        //获取主线程的名字,当然main也可以改名字
        System.out.println(Thread.currentThread().getName() + "---->main线程");
    }
多线程实现代码方式2

步骤:

  1. 定义类实现Runnable接口
  2. 实现run方法
  3. 把新线程要做的事写在run方法中
  4. 创建自定义的Runnable的子类对象
  5. 调用start()开启新线程,内部会自动调用Runnanle的run()方法
/**
Runnable匿名对象实现代码,并设置,获取名字
*/
    public static void main(String[] args) {
        new Thread(new Runnable(){
            public void run(){
                Thread.currentThread().setName("线程2");      //设置线程的名字
    System.out.println(Thread.currentThread().getName() + "---->线程2");
            }
        }).start();
        //获取主线程的名字,当然main也可以改名字
        System.out.println(Thread.currentThread().getName() + "---->main线程");
    }

这里扩展一下实现Runnable的原理
查看源码
* 1,看Thread类的构造函数,传递了Runnable接口的引用
* 2,通过init()方法找到传递的target给成员变量的target赋值
* 3,查看run方法,发现run方法中有判断,如果target不为null就会调用Runnable接口子类对象的run方法

两种实现方式的区别(import!!!)
* 查看源码的区别:
    * a.继承Thread : 由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法
    * b.实现Runnable : 构造函数中传入了Runnable的引用, 成员变量记住了它, start()调用run()方法时内部判断成员变量Runnable的引用是否为空, 不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法

* 继承Thread
    * 好处是:可以直接使用Thread类中的方法,代码简单
    * 弊端是:如果已经有了父类,就不能用这种方法
* 实现Runnable接口
    * 好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的
    * 弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂
主线程的获取Thread.currentThread()
    //设置主线程的名字  
Thread.currentThread().setName("我是主线程");        
    //直接写在main方法中,获取的是主线程       System.out.println(Thread.currentThread().getName());
休眠线程
  • Thread.sleep(毫秒,纳秒), 控制当前线程休眠若干毫秒1秒= 1000毫秒 1秒 = 1000 * 1000 * 1000纳秒 1000000000
 try {
//Sleep休眠,到时间后自动会醒来                                                                         //Wait方法,等待,没人叫不会醒  
        Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
      }
  • 1.什么情况下需要同步
    • 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
    • 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
      * 2.同步代码块
    • 使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
    • 多个同步代码块如果使用相同的锁对象, 那么他们就是同步的
同步方法
  • 使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的*

非静态的同步方法

//非静态的同步方法的锁对象是神马?
//答:非静态的同步方法的锁对象是this

静态的同步方法

//静态的同步方法的锁对象是什么?
//答:是该类的字节码对象

线程安全问题

下面通过模拟卖火车票的3个案例,来说明线程存在的各个安全问题

案例1:
//这样写代码,总共100张票,但是最终每个线程会卖100张,总共卖400张
package com.heima.syn;
    public class Demo3_Ticket { 
    /**  * 需求:铁路售票,一共100张,通过四个窗口卖完. 
    */  
        public static void main(String[] args) {    
            new Ticket().start();   
            new Ticket().start();
            new Ticket().start();   
            new Ticket().start();   
        }
     }

class Ticket extends Thread {   
    public void run() {     
        if(ticket == 0) {       
        }                   
            System.out.println(getName() + "...这是第" + ticket-- + "号票"); 
        }
    }
}
案例2:
//未加静态,出现卖负号票
package com.heima.syn;
    public class Demo3_Ticket {
/**  需求:铁路售票,一共100张,通过四个窗口卖完.
     */ 
         public static void main(String[] args) {   
            new Ticket().start();       
            new Ticket().start();   
            new Ticket().start();   
            new Ticket().start();
         }
     }
class Ticket extends Thread {   
    private static int ticket = 100;    
    public void run() { 
        while(true) {       
            if(ticket <= 0) { 
             //线程1睡,线程2睡,线程3睡,线程4睡  
                 break;     
             }          
                Thread.sleep(10);       
            } catch (InterruptedException e) {                                            
                 e.printStackTrace();       
            }             
                System.out.println(getName() + "...这是第"  + ticket-- + "号票");    
            }
    }
}
案例3:
//用同步实现
package com.heima.syn;
public class Demo3_Ticket {
    /**  * 需求:铁路售票,一共100张,通过四个窗口卖完. 
    public static void main(String[] args) {    
        new Ticket().start();       
        new Ticket().start();   
        new Ticket().start();   
        new Ticket().start();
    }
}
class Ticket extends Thread {   
    private static int ticket = 100;    
    //如果用引用数据类型成员变量当作锁对象,必须是静态的
    public void run() {     
        while(true) {       
             //加this是不行的,因为有四个线程对象          
             synchronized(Ticket.class) {           
                break;          
            try {               
            Thread.sleep(10);       
        //线程1睡,线程2睡,线程3睡,线程4睡           
                } catch (InterruptedException e) {                                      
                     e.printStackTrace();   
                }               
                    System.out.println(getName() + "...这
是第" + ticket-- + "号票");     
            }   
        }   
    }
}

锁对象

死锁:两个线程,各自拿着自己的锁,而又想获取对方的锁,而双方谁都不让,就会出现死锁
    多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁
>* 多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁
    * 尽量不要嵌套使用
    案例:
            private static String s1 = "筷子左";
            private static String s2 = "筷子右";
            public static void main(String[] args) {
                new Thread() {
                    public void run() {
                        while(true) {
                            synchronized(s1) {
                                System.out.println(getName() + "...拿到" + s1 + "等待" + s2);
                                synchronized(s2) {
                                    System.out.println(getName() + "...拿到" + s2 + "开吃");
                                }
                            }
                        }
                    }
                }.start();

                new Thread() {
                    public void run() {
                        while(true) {
                            synchronized(s2) {
                                System.out.println(getName() + "...拿到" + s2 + "等待" + s1);
                                synchronized(s1) {
                                    System.out.println(getName() + "...拿到" + s1 + "开吃");
                                }
                            }
                        }
                    }
                }.start();
            }

下面就是用多线程实现卖票并统计各个窗口卖票的个数的完整参考代码

package com.heima.texts;
/*
 * 总共100张张票,分两个窗口卖出,并统计两个窗口各卖出出的票数
 */
public class Demo17_Tickets{
    public static void main(String[] args) {
        Tickets4 t = new Tickets4();
        new Thread(t,"窗口A").start();
        new Thread(t,"窗口B").start();

    }
}

class Tickets4 implements Runnable {
    private int num = 100;
    private int counta = 0;         //统计窗口a卖出的票
    private int countb = 0;         //统计窗口b卖出的票
    @Override
    public void run() {
        while (true) {
            synchronized (this) {       //开启程序锁
                //获取线程名称
                String threadName = Thread.currentThread().getName();
                if(num <0) {
                    if ("窗口A".equals(threadName)) {
                        System.out.println(threadName + "卖出了:" + counta + "张");
                    }else {
                        System.out.println(threadName + "卖出了:" + countb + "张");
                    }
                    break;
                }
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                    if ("窗口A".equals(threadName)) {
                        counta++;
                    } else {
                        countb++;
                    }
                    System.out.println(threadName + "已卖出第:" + num+ "张,"+ " 还剩余:" + (100-num) + "张");
                    num--;
                }

            }
        }
    }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值