第十七节 多线程(二)购票经典案例+单例设计模式_runnable接口线程买票-程序员宅基地

技术标签: Java入门基础阶段  jvm  java  设计模式  

1. 多线程案例

  1. 这是一个经典的多线程案例,火车票售票案例,
    • 需求:假设一共100张火车票,多个窗口去售卖 。
  2. 想一想,在现实中是如果买票的,需要注意哪些事项 ?

1.1 窗口售票

1.1.1 方式一:继承 Thread 类

  1. 第一种继承Thread方式看看有什么效果!? 会出现什么样的问题!?
public class Test_Ticket {
    
    public static void main(String[] args) {
    
        TicketThread t = new TicketThread();
        TicketThread t1 = new TicketThread();
        TicketThread t2 = new TicketThread(); //在sleep()时增加一个窗口。
        t.start();
        t1.start();
        t2.start();//增加售卖窗口开始买票

    }
}
/*
    自定义类
 */

class TicketThread extends Thread{
    
    //int num_ticket =100;//1.定义总票数;
    static int  num_ticket =100;//使用static 全局变量定义总票数;
    //重写run() 方法
    @Override
    public void run() {
    
        //设置循环保证有票就要卖
        while (true){
    
            //2.TODO 注意:如果让线程休眠了(其目的就是为了让线程出错,增加了线程切换频率)。还能保证正确性么?
            try {
    
                Thread.sleep(10);//单位毫秒。
            } catch (InterruptedException e) {
    
                e.printStackTrace();
            }
            //打印卖票信息
            System.out.println(Thread.currentThread().getName()+" : "+num_ticket--);
            if (num_ticket<=0)break;//设置循环出口当没有票时就跳出。
        }
        //窗口提示消息
        if (num_ticket==0){
    
            System.out.println("票已经售完");
        }
    }
}
  1. 经过休眠之后产生的问题:
    • 产生第一个问题:出现了重复买票现象;

      在这里插入图片描述

    • 产生的第二个问题:出现了超卖的现象;
      在这里插入图片描述

1.1.2 方式二:实现Runnable接口

  1. 实现接口能解决相应问题么!?
//业务场景同Thread一样。
public class Test_Ticket2 {
    
    public static void main(String[] args) {
    
        MyRunnable r = new MyRunnable(); 
        Thread t = new Thread(r);
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);

        t.start();
        t1.start();
        t2.start();
        t3.start();

    }
}

//自定义类实现接口
class MyRunnable implements Runnable{
    
    int num_Ticket =100;

    //重写run()方法
    @Override
    public void run() {
    

        //循环一直卖票
        while (true){
    
            if (num_Ticket>0) {
     //假设我让票大于0在开始卖呢!?还会发生那样的问题么!?
            
            //TODO :制造睡眠,让线程产生频换切换,出现错误。
            try {
    
                Thread.sleep(10);
            } catch (InterruptedException e) {
    
                e.printStackTrace();
            }
                //打印 输出当前线程名称 + 票号
                System.out.println(Thread.currentThread().getName() + "= " + num_Ticket--);
            }
            //循环出口,当没有余票时,停止。
            if (num_Ticket<=0) break;
        }
    }
}

1.2 售票案例—同步锁

  1. 我们在上述 “售票案例” 中发现了多线程由于线程睡眠(产生现象线程的频换切换)导致了出现了问题: 超卖 和 重卖。
  2. 如何判断线程中有没有安全问题!?参考以下3点:
    • 这个程序时不是多线程!?
    • 在代码执行中有没有共享数据!?
    • 有没有多条语句去操作共享数据!?
  3. 以上的问题用什么解决方式: 到底该从哪方面入手呢!? 能不能把有问题的代码上一把锁,谁用谁开!?
    • 解决问题,就需要把有问题的代码,“全部包裹起来”,一次只让一个线程去执行,给上一把锁。
    • 让多线程对操作的共享数据,做到一个排队现象。如生活中的案例: 上厕所。

1.2.1 Synchronized

  1. 就是使用Synchronized将会引起线程安全问题的逻辑代码,用锁的形式,“锁起来”,只有持有这个锁的钥匙才能访问,并且,同一时刻,只有这一个线程持有这把锁的钥匙,从而确保线程安全。

    • 同步需要两个或者两个以上的线程。
    • 多个线程之间必须使用同一个锁。
    	sychronized(锁对象){
           // 这里的对象,相当于多线程的锁。线程相当于从该对象拿到了一个锁。
    		容易出问题的代码(需要共享的代码,涉及到共享操作的)
    	}
    
    
  2. 使用位置,锁的对象必须是唯一

    • 可以修饰 方法称为同步方法,会自动分配,使用的锁对象是this
    • 可以修饰代码块称为同步代码块,锁对象可以任意,但是必须唯一。
  3. 使用同步锁的特点:

    • 同步的缺点是会降低程序的执行效率, 对执行流程可控,但是为了保证线程安全,必须牺牲性能。
    • 要控制锁的范围。不需要给所有代码都上锁,例如:去卫生间给整个商场都上锁,就是不对滴。

1.3 使用同步锁改造售票案例

1.3.1 改造Thread 和 Runnable

  1. 锁的位置需要考虑,小了控制不住,大了又牺牲了性能。
    • 如何判断线程中有没有安全问题!?
public class Test_Ticket {
    
    public static void main(String[] args) {
    



        //创建线程
        MyThread t = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        MyThread t4 = new MyThread();

        t.start();
        t2.start();
        t3.start();
        t4.start();

    }
}

class MyThread extends Thread{
    
      static int num_Ticket=100; //票数(共享数据)
      //static Object o = new Object(); 这个也可以 也是全局唯一。
       Object o = new Object(); //创建唯一对象。 
    /*1.确定多线程隐患:3点:  是不是存在多个线程中?  存在共享数据!? 是否有多条语句操作该共享数据!?
      2.确定范围:范围太大,影响性能,范围太小,没有作用。                            
     */
    //重写run()方法
    @Override
    public void run() {
    
        while (true) {
    

            //TODO 制造困难 睡上一会。
            try {
    
                Thread.sleep(10);
            } catch (InterruptedException e) {
    
                e.printStackTrace();
            }

            //synchronized (new Object()){ // new Object()也是任意对象。 运行结果!? 没用!为什么?
            //synchronized (o){ // 不管你怎么调用我的o对象引用只有一次。 运行结果!? 还不行!? Why!?
            synchronized (MyThread.class){
     //使用本类。类对象锁定!!相当于锁住了本类。 运行结果: ok
            //TODO 售票的业务处理
            if (num_Ticket > 0) {
    
                System.out.println(getName() + " = " + num_Ticket--);
            }
            if (num_Ticket <= 0)break;//break,只能在循环里使用。

             }
        }

    }
}
  • 改造Runnable
public class Test_Runnable {
    
    public static void main(String[] args) {
    
        MyRunnable r = new MyRunnable();
        Thread t = new Thread(r);
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);
        Thread t4= new Thread(r);

        t.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

class MyRunnable implements Runnable{
    
    int num_Ticket =100; //票总数
    Object o =  new Object();
    @Override
    //synchronized public void run() { 可以直接在方法上加锁
         public void run() {
    
        //TODO 制造困难  睡觉10
        try {
    
            Thread.sleep(10);
        } catch (InterruptedException e) {
    
            e.printStackTrace();
        }

        while (true) {
    
          // synchroized(new Object()) // 对象不唯一。 
            synchronized (this) {
     //使用本类对象,指的是this  或者 // synchronized(o)也行
                if (num_Ticket > 0) {
    
                    System.out.println(Thread.currentThread().getName() + "=" + num_Ticket--);
                }
                if (num_Ticket <= 0) break;//循环出口。
            }
        }
    }
}
  • 还记得之前学过的StringBuffer 和StringBuilder:有看过它们底层实现么!!!?

2. 线程池

2.1 概述

  1. “池化技术的思想” 主要是为了减少每次获取资源的消耗,提高对资源的利用率。其目的是限制和管理资源,如:使用线程池可以进行统一的分配,调优和监控。
    • 降低资源消耗。
    • 提高相应速度。
    • 提高线程的可管理性。

2.2 线程池创建方式

2.2.1 ExecutorService/Executors

  1. ExecutorService 属于接口,用来存储线程的池子,把新建线程/启动线程/关闭线程的任务都交给池来管理。
           execute(Runnable任务对象) 把任务丢到线程池。
    
  2. Executors属于类,提供了工厂方法用来创建不同类型的线程池。
    		newFixedThreadPool(int nThreads) 最多n个线程的线程池
    		newCachedThreadPool() 足够多的线程,使任务不必等待
    		newSingleThreadExecutor() 只有一个线程的线程池
    

2.2.2 售票案例:用线程池创建线程

  • 通过线程池技术,改造售票案例 Runnable 接口。

    public class Test_Runnable {
          
        public static void main(String[] args) {
          
            MyRunnable r = new MyRunnable();
            // 使用线程池改造
          /*
            Thread t = new Thread(r);
            Thread t2 = new Thread(r);
            Thread t3 = new Thread(r);
            Thread t4= new Thread(r);
    
            t.start();
            t2.start();
            t3.start();
            t4.start();
           */
            //Todo 不想那么麻烦使用Thread 的start()方法去启动线程。 使用线程池技术来管理资源的启动。
            ExecutorService pool = Executors.newFixedThreadPool(3);//相当于启动3个线程。
           // pool.execute(r); //这只是取了一根线程。多线程怎么使用!? 通过循环。
            // 池子里有3个但是循环取了5个!? 相当于3个线程循环执行,提高使用率。如果循环少于线程数,相当于闲着线程。
            for (int i = 0; i <6 ; i++) {
          
                pool.execute(r);
            }
    
    
        }
    }
    
    class MyRunnable implements Runnable{
          
        int num_Ticket =100; //票总数
        Object o =  new Object();
        @Override
        //synchronized public void run() { 可以直接在方法上加锁
             public void run() {
          
            //TODO 制造困难  睡觉10
            try {
          
                Thread.sleep(10);
            } catch (InterruptedException e) {
          
                e.printStackTrace();
            }
    
            while (true) {
          
                synchronized (this) {
           //使用本类对象,指的是this  或者 // synchronized(o)也行
                    if (num_Ticket > 0) {
          
                        System.out.println(Thread.currentThread().getName() + "=" + num_Ticket--);
                    }
                    if (num_Ticket <= 0) break;//循环出口。
                }
            }
        }
    }
    

3. 线程锁

3.1 悲观锁和乐观锁

  1. 悲观锁:总是假设最坏的情况出现,用悲观的态度防止 并发间线程出现的冲突。
    • 在进行读写操作时,在释放锁之前,任何人都不能对其数据进行操作,直到前面释放锁之后,后一个对数据加锁在进行操作。同sychronized一样。
  2. 乐观锁:总是朝着最好的结果看待事情,理想的认为每次数据不会被修改,所以不需要持锁操作。
    • 但是在更新的时候会通过版本号机制或者算法(CAS,Compare And Swap),去判断一下是否发生过数据更改。

3.2 常见的锁(拓展知识)

  1. sychronized 互斥锁, (悲观锁)

    • 采用synchronized修饰符实现的同步机制叫做互斥锁机制。每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源。如果没有这个锁标记,任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。
  2. ReentrantLock 排他锁(悲观锁)

    • ReentrantLock是排他锁,又称独占锁。排他锁在同一时刻仅有一个线程可以进行访问,实际上是一种相对比较保守的锁策略。
    • 在这种情况下任何“读/读”、“读/写”、“写/写”操作都不能同时发生,这在一定程度上降低了吞吐量。例子:若操作者S 对数据对象A加上独占锁,则只允许操作者S读取和修改A,其他任何事务都不能再对A加任何类型的锁,直到释S放A上的锁。这就保证了S在释放A上的锁之前不能再读取和修改A。(然而读操作之间不存在数据竞争问题,如果”读/读”操作能够以共享锁的方式进行,那会进一步提升性能。) 所以需要读写锁。
  3. ReentrantReadWriteLock 读写锁(乐观锁)

    • 因此引入了ReentrantReadWriteLock顾名思义,ReentrantReadWriteLock是Reentrant(可重入,可重新在进入的意思)Read(读)Write(写)Lock(锁),我们下面称它为读写锁。读写锁内部又分为读锁和写锁, 读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。
    • 读锁和写锁分离从而提升程序性能,读写锁主要应用于读多写少的场景。

3.3 用读写锁改造售票案例

  1. 同悲观锁sychronized, 属于jvm底层级别,将读写都锁住了,其实多线程的 并不影响数据的完整性,主要是修改。所以使用ReentrantReadWriteLock 将写的过程控制,适用于读多写少的场景。

  2. ReentrantReadWriteLock分公平锁和非公平锁,公平锁即,排队顺序拿锁,先排先得,非公平不看排队顺序,可以插队。

    • 非公平锁性能比公平锁高5~10倍, 因为公平锁需要频繁唤醒队列中的线程,比较消耗资源。但是非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。
public class Test_Reentrant {
    
    public static void main(String[] args) {
    
        MyRunnable_Ticket r = new MyRunnable_Ticket();
        Thread t = new Thread(r);
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);

        t.start();
        t1.start();
        t2.start();

    }
}

class MyRunnable_Ticket implements Runnable{
    
    int num_ticket =100; //总票数

    //1. 创建读写锁,static 修饰保证全局唯一
    static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);//默认false 非公平模式,容易死锁,性能高。

    @Override
    public void run() {
    

        while (true) {
    
            /*
               2. 在操作资源前面上锁
             */
            lock.writeLock().lock();//上了写锁
            // Todo 制造睡眠,切换线程
            try {
    
                Thread.sleep(10);

                // 售票
                if (num_ticket > 0) {
    
                    System.out.println(Thread.currentThread().getName() + "=" + num_ticket--);
                }

                //不满足条件就跳出while循环
                if (num_ticket <= 0) break;

            } catch (InterruptedException e) {
    
                e.printStackTrace();
            }finally {
    
      
                lock.writeLock().unlock();//3. 不会像sychronized自动放锁,它需要手动释放。
            }
        }
    }
}

  1. 拓展知识: sychronized 也属于非公平锁,因为它获取锁的方式也是随机不排队的。
    • 二者区别在于:
      synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象, 因此使用 Lock 时需要在 finally 块中释放锁。

4. 设计模式

4.1 怎么理解设计模式!?

  1. 设计模式是 针对面向对象中反复出现问题的解决方案,是一套反复被开发者使用,并且多数人知晓的,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。 设计模式一共有23种之多,都是针对不同场景的解决方案。

在这里插入图片描述

4.2 单例设计模式(Singleton)

  1. 单例设计(singleton):一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点它最主要的特点就是保证在内存中该类只有一个实例对象。 这样做的好处,反复创建对象或者销毁,引起的资源浪费。
  2. 我们先看一个典型的单例模式设计的类 java.lang.Runtime ,分析下单例如何创建!?

4.2.1 单例模式一:饿汉式

  1. 啥是饿汉式!?是一种设计思想,举一个生活例子:一个很饿的人,它很勤快就怕自己饿着,提前准备食物,什么时候想吃,拿过来就吃。
    • 即饿汉式在 开始类加载的时候就已经实例化,并且创建单例对象,用的时候拿过来就用。
/**
     饿汉式
 */
public class Test_Singleton {
    
    public static void main(String[] args) {
    

        //4.测试
        MySingLeton s = MySingLeton.getSingLeton();
        System.out.println(s);

        //4.1 怎么测试创建成功,地址值相同即可! s==s1。
        MySingLeton s1 = MySingLeton.getSingLeton();
        System.out.println(s1);

    }
}

class MySingLeton{
    
    //1.私有化 无参构造方法,无参通过构造方法创建对象。
    private MySingLeton(){
    }

    //2.在类的内部,创建本类对象,私有化该对象
    //2.1 当提供的对外访问方法用static 修饰后 创建本类对象也需要用static修饰,静态只能调用静态
    // private MySingLeton singLeton = new MySingLeton();
    static private MySingLeton singLeton = new MySingLeton();

    //3.提供对外访问方法。
    //3.1 无法创建对象,怎么访问该方法!? 所以需要static 修饰,通过类名.方法调用!
    // public MySingLeton getSingLeton() {
    
   static public MySingLeton getSingLeton() {
    
        return singLeton; // 为什么!?会报错,因为静态只能调用静态
    }
}

4.2.2 单例模式二: 懒汉式(面试知识点)

  1. 啥是懒汉式!?生活中的例子:因为它比较懒么!?什么时候饿了,什么时候在想办法搞点食物!
    • 即懒汉式: 开始不会实例化什么时候用就什么时候new,才进行实例化 需要时在使用,相当于 延迟加载。
/**
 * 懒汉式:面试重点知识,同饿汉式创建方式大体相似,但是有细微不同!
 *  设计到两个层面,1,什么是延迟加载!?
 *                2,如何解决线程安全!?在多线程的情况下。
 *
 */
public class Test_SingLeton2 {
    
    public static void main(String[] args) {
    

        MySingLeton2 mySingLeton = MySingLeton2.getMySingLeton();
        System.out.println(mySingLeton);
        MySingLeton2 mySingLeton2 = MySingLeton2.getMySingLeton();
        System.out.println(mySingLeton2==mySingLeton);//判断是否一致。
    }
}
class MySingLeton2{
    

   static Object obj =  new Object();
    //1.私有化无参构造方法,目的:防止任意创建对象。
    private MySingLeton2(){
    }

    //2.在本类中,创建本类的对象。并私有化。
    static private MySingLeton2  mySingLeton ; //2.1懒汉式,相当于什么时候需要什么时候在创建!--延迟加载!

    //3.对外提供访问方法
     static public MySingLeton2 getMySingLeton(){
    
        //4.关键点,判断什么时候需要在创建!
        //4.1 问题:该模式存在多个数据操作! 存在线程安全隐患!?怎么解决!?
        synchronized (obj) {
     // 这样相当于将整个内容全部包裹进来!也可以是用类锁
            if (mySingLeton == null) {
    
                mySingLeton = new MySingLeton2();
            }
            return mySingLeton;
        }
    }
}

4.2.3 单例模式:懒汉式和饿汉式的区别

  1. 线程安全方面:
    • 饿汉式: 线程不存在多数据操作,线程安全。
    • 懒汉式:线程不安全,需要锁机制处理。
  2. 效率和内存空间:
    • 饿汉式: 因为没有锁机制, 执行效率高,但是一开始就会加载对象,不管是否使用都会占用内存。
    • 懒汉式:有锁机制,效率比饿汉式差点,但是不占用内存空间,因为什么时候使用什么时候创建。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u013280750/article/details/126052973

智能推荐

Docker 快速上手学习入门教程_docker菜鸟教程-程序员宅基地

文章浏览阅读2.5w次,点赞6次,收藏50次。官方解释是,docker 容器是机器上的沙盒进程,它与主机上的所有其他进程隔离。所以容器只是操作系统中被隔离开来的一个进程,所谓的容器化,其实也只是对操作系统进行欺骗的一种语法糖。_docker菜鸟教程

电脑技巧:Windows系统原版纯净软件必备的两个网站_msdn我告诉你-程序员宅基地

文章浏览阅读5.7k次,点赞3次,收藏14次。该如何避免的,今天小编给大家推荐两个下载Windows系统官方软件的资源网站,可以杜绝软件捆绑等行为。该站提供了丰富的Windows官方技术资源,比较重要的有MSDN技术资源文档库、官方工具和资源、应用程序、开发人员工具(Visual Studio 、SQLServer等等)、系统镜像、设计人员工具等。总的来说,这两个都是非常优秀的Windows系统镜像资源站,提供了丰富的Windows系统镜像资源,并且保证了资源的纯净和安全性,有需要的朋友可以去了解一下。这个非常实用的资源网站的创建者是国内的一个网友。_msdn我告诉你

vue2封装对话框el-dialog组件_<el-dialog 封装成组件 vue2-程序员宅基地

文章浏览阅读1.2k次。vue2封装对话框el-dialog组件_

MFC 文本框换行_c++ mfc同一框内输入二行怎么换行-程序员宅基地

文章浏览阅读4.7k次,点赞5次,收藏6次。MFC 文本框换行 标签: it mfc 文本框1.将Multiline属性设置为True2.换行是使用"\r\n" (宽字符串为L"\r\n")3.如果需要编辑并且按Enter键换行,还要将 Want Return 设置为 True4.如果需要垂直滚动条的话将Vertical Scroll属性设置为True,需要水平滚动条的话将Horizontal Scroll属性设_c++ mfc同一框内输入二行怎么换行

redis-desktop-manager无法连接redis-server的解决方法_redis-server doesn't support auth command or ismis-程序员宅基地

文章浏览阅读832次。检查Linux是否是否开启所需端口,默认为6379,若未打开,将其开启:以root用户执行iptables -I INPUT -p tcp --dport 6379 -j ACCEPT如果还是未能解决,修改redis.conf,修改主机地址:bind 192.168.85.**;然后使用该配置文件,重新启动Redis服务./redis-server redis.conf..._redis-server doesn't support auth command or ismisconfigured. try

实验四 数据选择器及其应用-程序员宅基地

文章浏览阅读4.9k次。济大数电实验报告_数据选择器及其应用

随便推点

灰色预测模型matlab_MATLAB实战|基于灰色预测河南省社会消费品零售总额预测-程序员宅基地

文章浏览阅读236次。1研究内容消费在生产中占据十分重要的地位,是生产的最终目的和动力,是保持省内经济稳定快速发展的核心要素。预测河南省社会消费品零售总额,是进行宏观经济调控和消费体制改变创新的基础,是河南省内人民对美好的全面和谐社会的追求的要求,保持河南省经济稳定和可持续发展具有重要意义。本文建立灰色预测模型,利用MATLAB软件,预测出2019年~2023年河南省社会消费品零售总额预测值分别为21881...._灰色预测模型用什么软件

log4qt-程序员宅基地

文章浏览阅读1.2k次。12.4-在Qt中使用Log4Qt输出Log文件,看这一篇就足够了一、为啥要使用第三方Log库,而不用平台自带的Log库二、Log4j系列库的功能介绍与基本概念三、Log4Qt库的基本介绍四、将Log4qt组装成为一个单独模块五、使用配置文件的方式配置Log4Qt六、使用代码的方式配置Log4Qt七、在Qt工程中引入Log4Qt库模块的方法八、获取示例中的源代码一、为啥要使用第三方Log库,而不用平台自带的Log库首先要说明的是,在平时开发和调试中开发平台自带的“打印输出”已经足够了。但_log4qt

100种思维模型之全局观思维模型-67_计算机中对于全局观的-程序员宅基地

文章浏览阅读786次。全局观思维模型,一个教我们由点到线,由线到面,再由面到体,不断的放大格局去思考问题的思维模型。_计算机中对于全局观的

线程间控制之CountDownLatch和CyclicBarrier使用介绍_countdownluach于cyclicbarrier的用法-程序员宅基地

文章浏览阅读330次。一、CountDownLatch介绍CountDownLatch采用减法计算;是一个同步辅助工具类和CyclicBarrier类功能类似,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。二、CountDownLatch俩种应用场景: 场景一:所有线程在等待开始信号(startSignal.await()),主流程发出开始信号通知,既执行startSignal.countDown()方法后;所有线程才开始执行;每个线程执行完发出做完信号,既执行do..._countdownluach于cyclicbarrier的用法

自动化监控系统Prometheus&Grafana_-自动化监控系统prometheus&grafana实战-程序员宅基地

文章浏览阅读508次。Prometheus 算是一个全能型选手,原生支持容器监控,当然监控传统应用也不是吃干饭的,所以就是容器和非容器他都支持,所有的监控系统都具备这个流程,_-自动化监控系统prometheus&grafana实战

React 组件封装之 Search 搜索_react search-程序员宅基地

文章浏览阅读4.7k次。输入关键字,可以通过键盘的搜索按钮完成搜索功能。_react search