icode icode
首页
  • Android学习

    • 📁基础内容
    • 📺AndroidCore
    • 🎨Android-UI
    • 🏖️Components
    • 📊Fragment
    • 🔗网络操作
    • 🔏异步机制
    • 📦数据存储
    • 🗃️Gradle
  • 学习笔记

    • 『框架』笔记
    • 『Kotlin』笔记
    • 《Vue》笔记
    • 《Git》学习笔记
    • 『Bug踩坑记录』
  • ListView
  • RecyclerView
  • ViewPager
  • Java笔记

    • 🟠JavaSE
    • 🟢JavaWeb
    • 🔴JavaEE
    • ⚪JavaTopic
    • 🍳设计模式
  • 计算机基础

    • 📌计算机网络
    • 🔍数据结构
    • 📦数据库
    • 💻OS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
  • 关于

    • 📫关于我
  • 收藏

    • 网站
    • 资源
    • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

iqqcode

保持对技术的探索实践与热爱
首页
  • Android学习

    • 📁基础内容
    • 📺AndroidCore
    • 🎨Android-UI
    • 🏖️Components
    • 📊Fragment
    • 🔗网络操作
    • 🔏异步机制
    • 📦数据存储
    • 🗃️Gradle
  • 学习笔记

    • 『框架』笔记
    • 『Kotlin』笔记
    • 《Vue》笔记
    • 《Git》学习笔记
    • 『Bug踩坑记录』
  • ListView
  • RecyclerView
  • ViewPager
  • Java笔记

    • 🟠JavaSE
    • 🟢JavaWeb
    • 🔴JavaEE
    • ⚪JavaTopic
    • 🍳设计模式
  • 计算机基础

    • 📌计算机网络
    • 🔍数据结构
    • 📦数据库
    • 💻OS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
  • 关于

    • 📫关于我
  • 收藏

    • 网站
    • 资源
    • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • JavaSE

    • 面向对象

    • 常用API

    • 集合类

    • 多线程

      • Thread

        • 进程与线程
        • 黄牛卖票和模拟龟兔赛跑问题分析
          • 线程常用方法
          • 线程停止及守护线程
          • synchronized关键字解决线程安全问题
          • synchnized原理及优化
          • 线程间的通信
          • 死锁
          • Lock体系
          • ReentrantLock
          • 线程池使用
          • 线程池原理及配置
          • Java线程池的四种用法与使用场景
          • ThreadLocal
        • JUC

      • JDK8新特性

      • IO流

      • JVM

    • JavaWeb

    • JavaEE

    • JavaTopic

    • 设计模式

    • 计算机基础

    • Java后端
    • JavaSE
    • 多线程
    • Thread
    iqqcode
    2021-06-17
    目录

    黄牛卖票和模拟龟兔赛跑问题分析

    # 数据共享带来的并发问题

    多线程有两个经典案例,12306买票问题和上厕所问题😂

    一份资源,多个线程共享,会带来并发问题(数据的不一致性)

    在实例化Web12306的时候,要想达到20张票,3个黄牛同时在卖,就只能实例化一次;

    • 如果实例化三次,则成为每个黄牛在卖20张票,不会产生并发问题,因为此时每一个线程都得带到了一份资源

    为了模拟买票时的网络延迟,Sleep 200ms 但得的结果却是:【不存在的票】

    20份票的资源给3个黄牛来卖,票显然不会出现负数,这就是并发带来的数据不一致的问题

    具体的原因:

    首先我们要明白的一点就是,CPU执行的速度非常的快,用ms甚至是ns来衡量

    当只剩最后一张票时,假设线程1、线程2、线程3分别被CPU调度:

    1. tickets = 1 > 0,线程1先进入到while循环中,休眠200ms,交出了CPU执行权;
    2. 此时,CPU空闲,调度线程2,由于线程1在休眠中还没有卖出票,此时 tickets = 1;
    3. 线程2进入while循环,也休眠200ms。此时tickets仍然等于1;
    4. 由于CPU切换的速度非常快,此时线程1和线程2仍然在休眠中,CPU调度线程3;
    5. 线程3进入while循环,同样休眠200ms
    6. 线程1最先休眠,所以最先醒来。执行tickets--操作,tickets = 0,此时线程2和线程3仍在休眠中
    7. 线程2睡眠完成,由于线程2和线程3此时已经进入到了循环中,所以可以继续执行。线程2执行tickets--操作,tickets = -1;
    8. 同理,线程三醒来后又执行tickets--,tickets = -2

    还有可能得到的结果是:【卖重复的票】

    同一份资源,被三个线程同时拿到

    每个线程在工作的时候,都会在相应的栈中执行,一个线程开辟一个栈空间(忘了的伙伴看看上面线程执行的内存图),它们从内存中读取数据。

    比如在卖第10张票时,A线程从主存中拿到了10;由于CPU的操作非常的块,在A把10拿到他的工作区间的时候,线程B和线程C也同时从主存中拿到了各自的工作区间;所以就导致第10张票被卖了3次...


    注意:

    线程安全问题是不能产生的,我们可以让一个线程在访问共享数据的时候,无论是否失去了对CPU的执行权,让其他的线程只能等待当前占有CPU的线程执行完其所有的操作,其他线程才能获取资源(等待当前线程卖完票,其他线程在进行卖票)

    保证:同一时刻只有一个线程在卖票,即在上面的例子中,只有一个线程在while循环中进行买票

    所以,针对此问题,Java中提出了锁机制

    通过synchronized关键字来给对象上锁

    👉线程的同步 (opens new window)👈


    # 多线程模拟实现龟兔赛跑

    故事背景:

    从前有座山,山上有个乌龟和兔子,然后他们比赛,然后兔子睡着了,然后乌龟赢了,然后就没有然后了....

    class Running implements Runnable {
        private  String winner;  //获胜者名字
        @Override
        public void run() {
            for(int steps = 1;steps<=100;steps++) {
                //模拟兔子休息,让兔子每走10步睡300ms
                if(Thread.currentThread().getName().equals("rabbit") && steps%10==0) {
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() +"---->" + steps);
                //判断比赛是否结束
                boolean flag = isOver(steps);
                if(flag)
                    break;
            }
        }
    
        private boolean isOver(int steps) {
            if(winner != null)
                return true;
            else {
                if(steps == 100) {
                    winner = Thread.currentThread().getName();
                    System.out.println("\n***** Winner is " + winner + " *****");
                    return true;
                }
            }
            return false;
        }
    }
    
    
    public class ThreadExample {
        public static void main(String[] args) {
            Running run = new Running();
            new Thread(run,"tortoise").start();
            new Thread(run,"rabbit").start();
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43

    最终,兔子走了10步,乌龟走了100步,Winner为 乌龟

    这个故事告诉我们:

    1. 腿长的不一定走得快
    2. 时间再短也可以睡觉
    3. 在这个故事里,赢得总会是乌龟;因为编故事的人不想让兔子赢,不然还比什么赛呀....兔子要想变的像博尔特辣样快的蓝人,还和乌龟比什么,直接去找猎豹比了,破釜沉舟...
    编辑 (opens new window)
    上次更新: 2021/06/27, 10:49:09
    进程与线程
    线程常用方法

    ← 进程与线程 线程常用方法→

    最近更新
    01
    匿名内部类
    10-08
    02
    函数式接口
    10-08
    03
    ARouter-Kotlin踩坑
    10-05
    更多文章>
    Theme by Vdoing | Copyright © 2021-2023 iqqcode | MIT License | 备案号-京ICP备2021028793号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式
    ×