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

两种方式实现

A:继承Thread类

B:实现Runnable接

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

  1. package cn.itcast_06;
  2. public class SellTicket extends Thread {
  3. // 定义100张票
  4. // private int tickets = 100;这时候仍然是创建的3个对象都有tickets = 100的成员变量,这样还是不行的
  5. // 为了让多个线程对象共享这100张票,我们其实应该用静态修饰
  6. private static int tickets = 100;
  7. @Override
  8. public void run() {
  9. // 定义100张票
  10. // 每个线程进来都会走这里,这样的话,每个线程对象相当于买的是自己的那100张票,这不合理,所以应该定义到外面
  11. // int tickets = 100;
  12. // 是为了模拟一直有票
  13. while (true) {
  14. if (tickets > 0) {
  15. System.out.println(getName() + "正在出售第" + (tickets--) + "张票");
  16. }
  17. }
  18. }
  19. }
  1. package cn.itcast_06;
  2. /*
  3. * 某电影院目前正在上映贺岁大片(红高粱,少林寺传奇藏经阁),共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
  4. * 继承Thread类来实现。
  5. */
  6. public class SellTicketDemo {
  7. public static void main(String[] args) {
  8. // 创建三个线程对象
  9. SellTicket st1 = new SellTicket();
  10. SellTicket st2 = new SellTicket();
  11. SellTicket st3 = new SellTicket();
  12. // 给线程对象起名字
  13. st1.setName("窗口1");
  14. st2.setName("窗口2");
  15. st3.setName("窗口3");
  16. // 启动线程
  17. st1.start();
  18. st2.start();
  19. st3.start();
  20. }
  21. }

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

  1. package cn.itcast_07;
  2. public class SellTicket implements Runnable {
  3. // 定义100张票
  4. private int tickets = 100;//这里没有加static修饰,因为下面我们只创建一个对象,通过创建3个线程来享用这一个对象资源
  5. @Override
  6. public void run() {
  7. while (true) {
  8. if (tickets > 0) {
  9. System.out.println(Thread.currentThread().getName() + "正在出售第"
  10. + (tickets--) + "张票");
  11. }
  12. }
  13. }
  14. }
  1. package cn.itcast_07;
  2. /*
  3. * 实现Runnable接口的方式实现
  4. */
  5. public class SellTicketDemo {
  6. public static void main(String[] args) {
  7. // 创建资源对象
  8. SellTicket st = new SellTicket();
  9. // 创建三个线程对象
  10. Thread t1 = new Thread(st, "窗口1");
  11. Thread t2 = new Thread(st, "窗口2");
  12. Thread t3 = new Thread(st, "窗口3");
  13. // 启动线程
  14. t1.start();
  15. t2.start();
  16. t3.start();
  17. }
  18. }

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

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

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

->每次卖票延迟100毫秒

但是还是出现问题了:

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

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

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

随机性和延迟导致的

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

  1. package cn.itcast_08;
  2. public class SellTicket implements Runnable {
  3. // 定义100张票
  4. private int tickets = 100;
  5. // @Override
  6. // public void run() {
  7. // while (true) {
  8. // // t1,t2,t3三个线程
  9. // // 这一次的tickets = 100;
  10. // if (tickets > 0) {
  11. // // 为了模拟更真实的场景,我们稍作休息
  12. // try {
  13. // Thread.sleep(100); // t1就稍作休息,t2就稍作休息
  14. // } catch (InterruptedException e) {
  15. // e.printStackTrace();
  16. // }
  17. //
  18. // System.out.println(Thread.currentThread().getName() + "正在出售第"
  19. // + (tickets--) + "张票");
  20. // // 理想状态:
  21. // // 窗口1正在出售第100张票
  22. // // 窗口2正在出售第99张票
  23. // // 但是呢?
  24. // // CPU的每一次执行必须是一个原子性(最简单基本的)的操作。
  25. // // 先记录以前的值
  26. // // 接着把ticket--
  27. // // 然后输出以前的值(t2来了)
  28. // // ticket的值就变成了99
  29. // // 窗口1正在出售第100张票
  30. // // 窗口2正在出售第100张票
  31. //
  32. // }
  33. // }
  34. // }
  35. @Override
  36. public void run() {
  37. while (true) {
  38. // t1,t2,t3三个线程
  39. // 这一次的tickets = 1;
  40. if (tickets > 0) {
  41. // 为了模拟更真实的场景,我们稍作休息
  42. try {
  43. Thread.sleep(100); //t1进来了并休息,t2进来了并休息,t3进来了并休息,
  44. } catch (InterruptedException e) {
  45. e.printStackTrace();
  46. }
  47. System.out.println(Thread.currentThread().getName() + "正在出售第"
  48. + (tickets--) + "张票");
  49. //窗口1正在出售第1张票,tickets=0
  50. //窗口2正在出售第0张票,tickets=-1
  51. //窗口3正在出售第-1张票,tickets=-2
  52. }
  53. }
  54. }
  55. }
  1. package cn.itcast_08;
  2. /*
  3. * 实现Runnable接口的方式实现
  4. *
  5. * 通过加入延迟后,就产生了连个问题:
  6. * A:相同的票卖了多次
  7. * CPU的一次操作必须是原子性的
  8. * B:出现了负数票
  9. * 随机性和延迟导致的
  10. */
  11. public class SellTicketDemo {
  12. public static void main(String[] args) {
  13. // 创建资源对象
  14. SellTicket st = new SellTicket();
  15. // 创建三个线程对象
  16. Thread t1 = new Thread(st, "窗口1");
  17. Thread t2 = new Thread(st, "窗口2");
  18. Thread t3 = new Thread(st, "窗口3");
  19. // 启动线程
  20. t1.start();
  21. t2.start();
  22. t3.start();
  23. }
  24. }

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

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

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

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

 A是否是多线程环境

 B是否有共享数据

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

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

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

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

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

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

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

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

 

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

  1. package cn.itcast_10;
  2. public class SellTicket implements Runnable {
  3. // 定义100张票
  4. private int tickets = 100;
  5. // 定义同一把锁
  6. private Object obj = new Object();
  7. @Override
  8. public void run() {
  9. while (true) {
  10. // t1,t2,t3都能走到这里
  11. // 假设t1抢到CPU的执行权,t1就要进来
  12. // 假设t2抢到CPU的执行权,t2就要进来,发现门是关着的,进不去。所以就等着。
  13. // 门(开,关)
  14. synchronized (obj) { // 发现这里的代码将来是会被锁上的,所以t1进来后,就锁了。(关)
  15. if (tickets > 0) {
  16. try {
  17. Thread.sleep(100); // t1就睡眠了
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. System.out.println(Thread.currentThread().getName()
  22. + "正在出售第" + (tickets--) + "张票 ");
  23. //窗口1正在出售第100张票
  24. }
  25. } //t1就出来可,然后就开门。(开)
  26. }
  27. }
  28. }
  1. package cn.itcast_10;
  2. /*
  3. * 举例:
  4. * 火车上厕所。
  5. *
  6. * 同步的特点:
  7. * 前提:
  8. * 多个线程
  9. * 解决问题的时候要注意:
  10. * 多个线程使用的是同一个锁对象
  11. * 同步的好处
  12. * 同步的出现解决了多线程的安全问题。
  13. * 同步的弊端
  14. * 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
  15. */
  16. public class SellTicketDemo {
  17. public static void main(String[] args) {
  18. // 创建资源对象
  19. SellTicket st = new SellTicket();
  20. // 创建三个线程对象
  21. Thread t1 = new Thread(st, "窗口1");
  22. Thread t2 = new Thread(st, "窗口2");
  23. Thread t3 = new Thread(st, "窗口3");
  24. // 启动线程
  25. t1.start();
  26. t2.start();
  27. t3.start();
  28. }
  29. }

Android(java)学习笔记67:多线程程序练习的更多相关文章

  1. 【原】Java学习笔记032 - 多线程

    package cn.temptation; public class Sample01 { public static void main(String[] args) { /* * [进程]:正在 ...

  2. Java学习笔记:多线程(一)

    Java中线程的五种状态: 新建状态(New) 就绪状态(Runnable) 运行状态(Running) 阻塞状态(Blocked) 凋亡状态(Dead) 其中阻塞状态(Blocked)又分为三种: ...

  3. Java学习笔记之——多线程

    多线程编程 程序: 进程:一个程序运行就会产生一个进程 线程:进程的执行流程,一个进程至少有一个线程,称为主线程 如:QQ聊着天,同时在听音乐 一个进程可以有多个线程,多个线程共享同一个进程的资源 线 ...

  4. java学习笔记(5)多线程

    一.简介(过段时间再写,多线程难度有点大) --------------------------------------- 1.进程:运行时的概念,运行的应用程序 2.线程:应用程序内部并发执行的代码 ...

  5. Java 学习笔记(11)——多线程

    Java内部提供了针对多线程的支持,线程是CPU执行的最小单位,在多核CPU中使用多线程,能够做到多个任务并行执行,提高效率. 使用多线程的方法 创建Thread类的子类,并重写run方法,在需要启动 ...

  6. Java学习笔记:多线程(二)

    与线程生命周期相关的方法: sleep 调用sleep方法会进入计时等待状态,等待时间到了,进入就绪状态. yield 调用yield方法会让别的线程执行,但是不确保真正让出.较少使用,官方注释都说 ...

  7. JAVA学习笔记系列2-Java程序的运行机制

    计算机高级语言的类型主要有编译型和解释型两种,而java语言是两种类型的结合. java首先利用文本编辑器编写java源程序,源文件后缀名为.java,再利用编译器(javac)将源程序编译成字节码文 ...

  8. 0037 Java学习笔记-多线程-同步代码块、同步方法、同步锁

    什么是同步 在上一篇0036 Java学习笔记-多线程-创建线程的三种方式示例代码中,实现Runnable创建多条线程,输出中的结果中会有错误,比如一张票卖了两次,有的票没卖的情况,因为线程对象被多条 ...

  9. java学习笔记(1)java的基础介绍 、JDK下载、配置环境变量、运行java程序

    java工程师是开发软件的 什么是软件呢? 计算机包括两部分: 硬件: 鼠标.键盘.显示器.主机箱内部的cpu.内存条.硬盘等 软件: 软件包括:系统软件和应用软件 系统软件:直接和硬件交互的软件:w ...

  10. Java学习笔记-多线程-创建线程的方式

    创建线程 创建线程的方式: 继承java.lang.Thread 实现java.lang.Runnable接口 所有的线程对象都是Thead及其子类的实例 每个线程完成一定的任务,其实就是一段顺序执行 ...

随机推荐

  1. Advanced Scene Processing

    [How a Scene Processes Frames of Animation] In the traditional view system, the contents of a view a ...

  2. Network Object NAT配置介绍

    1.Dynamic NAT(动态NAT,动态一对一) 实例一: 传统配置方法: nat (Inside) 1 10.1.1.0 255.255.255.0 global (Outside) 1 202 ...

  3. 咏南多层开发框架支持最新的DELPHI 10 SEATTLE

    购买了咏南多层开发框架的老用户如有需要提供免费升级. 中间件

  4. find命令之xargs

    在使用 find命令的-exec选项处理匹配到的文件时, find命令将所有匹配到的文件一起传递给exec执行.但有些系统对能够传递给exec的命令长度有限制,这样在find命令运行几分钟之后,就会出 ...

  5. cf754 B. Ilya and tic-tac-toe game

    呵呵呵,这个题简直是一直在乱做,真是最近太弱了 #include<bits/stdc++.h> #define lowbit(x) x&(-x) #define LL long l ...

  6. 无责任Windows Azure SDK .NET开发入门篇三[使用Azure AD 管理用户信息--3.4 Edit修改用户信息]

    3.4 Edit修改用户信息 我们用FormCollection简化了表单提交,非常方便的进行用户信息修改. [HttpPost, Authorize] public async Task<Ac ...

  7. FLEX4中的Panel如何实现带自定义图标和按钮

      做过flex开发的程序员都知道,使用flex3中的panel自定义按钮很容易,而且flex3的panel有icon属性.但是flex4的中大部分的控件与flex3中的控件实现方式有很大的变化,同是 ...

  8. VPW协议解析

    http://www.dpfdoctor.net/content/?220.html SAE J1850 VPW协议也是OBD II标准中的一种,通常应用于GM车系中. VPW英文全称是Variabl ...

  9. nginx-1.4.4 + tcp_proxy_module手动编译安装

    Nginx开源软件默认没有提供TCP协议的负载均衡,下面记录一下我的安装过程: 1. 下载nginx最新稳定版的源码 mkdir /software cd /software yum install ...

  10. MySQL数据库能够用随意ip连接訪问的方法

    通过CMD命令行改动数据库表的一个字段的值.实现连接,訪问. 第一步.找到MYSQL软件安装所在的bin文件夹. (1)cd\当前文件夹 (2)指定MYSQL安装的bin文件夹 (3)输入 -h lo ...