AQS共享模式之CyclicBarrier

news2025/1/13 2:37:46

概念:CyclicBarrier翻译为循环(屏障/栅栏),当一组线程到达一个屏障(同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会打开,所有被屏障拦截的线程才会继续工作。

设计目的:和CountDownLatch都为同步辅助类,因为CountDownLatch的计数器是一次性的,属于一对多,

1、【某一个线程需要其余线程执行完之后再执行 (一个等多个)】,

2、【一些线程需要在某个时刻同时执行,就像等待裁判员枪响后,才能同时起跑(多个等一个)】

所以,CyclicBarrier被设计成为计数器可循环使用,多对多。只要多个线程都达到后,自会执行接下来的事,没有CountDownLatch的一个等多个,多个等一个的现象。

源码解析:

public class CyclicBarrier {
    /**
     * Each use of the barrier is represented as a generation instance.
     * The generation changes whenever the barrier is tripped, or
     * is reset. There can be many generations associated with threads
     * using the barrier - due to the non-deterministic way the lock
     * may be allocated to waiting threads - but only one of these
     * can be active at a time (the one to which {@code count} applies)
     * and all the rest are either broken or tripped.
     * There need not be an active generation if there has been a break
     * but no subsequent reset.
     * 屏障的每次使用都表示为一个生成实例。当屏障被触发或重置时,生成就会改变。
     可以有许多代与使用barrier的线程相关联——由于锁可能以不确定的方式分配给等待线程——但是一次只能有一个是活动的({@code count}应用的那个),
     其余的要么中断,要么被触发。如果有中断,但没有随后的重置,则不需要活动代。
     */
    private static class Generation {
        boolean broken = false;
    }

    /** The lock for guarding barrier entry
    保护屏障的锁,重入锁,调用await()方法的时候会用到,只有一个线程会执行成功,所以多线程高并发的情况下CyclicBarrier的执行效率不是很高 */
    private final ReentrantLock lock = new ReentrantLock();
    /** Condition to wait on until tripped
       待跳闸的条件( Condition本身用于线程间通信,在这就是阻塞和唤醒线程用的)*/
    private final Condition trip = lock.newCondition();
    /** The number of parties
    这个就是计数器初始值,新建类的时候传入的数值会赋值给他 */
    private final int parties;
    /* The command to run when tripped 
       跳闸时要执行操作的线程*/
    private final Runnable barrierCommand;
    /** The current generation 
       当前代*/
    private Generation generation = new Generation();

    /**
     * Number of parties still waiting. Counts down from parties to 0
     * on each generation.  It is reset to parties on each new
     * generation or when broken.
       还有很多任务在等待。每一代从计数器初始值(parties)降到0。它会在每一代新创建或用尽时重置。
     */
    private int count;

    /**
     * Updates state on barrier trip and wakes up everyone.
     * Called only while holding lock.
     只在持有锁时调用,去更新屏障的状态并唤醒其他线程。(意思代表下一代,其实就是新建一个代对象重新赋值)
     */
    private void nextGeneration() {
        // signal completion of last generation
        //当计数器减少为零证明这代已经满了,唤醒所有当前代阻塞的线程,这是Condition的方法
        trip.signalAll();
        // set up next generation
        //将初始需要拦截的计数器初始值初始化给count计数器
        count = parties;
        //上一代已经结束了,就要重新开启下一代
        generation = new Generation();
    }

    /**
    这个方法就是告诉这一代线程出问题了: 屏障里面的某个线程中断了,那就唤醒所有线程,然后这个屏障拦截的所有线程都会被唤醒,然后抛出异常,并告诉这一代坏掉了
     * Sets current barrier generation as broken and wakes up everyone.
     * Called only while holding lock.
     */
    private void breakBarrier() {
        //将这代标记为true,告诉已经坏掉了
        generation.broken = true;
        //这一代重新计数
        count = parties;
        //唤醒这一代里面的所有阻塞线程
        trip.signalAll();
    }

    /**
     * Main barrier code, covering the various policies.
     主要的屏障代码,涵盖了各种策略
     */
    private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
                   //当多线程并发情况下只有一个线程能获得锁(注意:该锁为jvm级锁,只能保证单实例有效,多进程下管理共享资源无效)
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //获取当前代的信息
            final Generation g = generation;
                //调用await方法之前需要判断这一代是否已经坏掉了,如果坏掉了直接抛出异常
            if (g.broken)
                throw new BrokenBarrierException();
            //判断当前线程是否中断了,如果中断了也会抛出异常,并调用breakBarrier方法
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }
//如果这代没有坏掉,当前线程也没有中断,那么就将计数器减去1
            int index = --count;
            //减去1之后的数值如果为零,证明这一代已经满了,然后唤醒屏障拦截的所有线程
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    //获取初始化的那个线程或者传入的线程
                    final Runnable command = barrierCommand;
                    if (command != null)
                    //线程执行任务
                        command.run();
                    ranAction = true;
                     //执行换代操作
                    nextGeneration();
                    return 0;
                } finally {
                     //如果上的操作执行出现问题了,那么就执行breakBarrier方法,唤醒所有线程
                    if (!ranAction)
                        breakBarrier();
                }
            }

            // loop until tripped, broken, interrupted, or timed out
            //自旋
            for (;;) {
                try {
                     //await()方法可以传入两个参数的,一个是否需要判断等待时间,一个等待的时间
                    //如果不需要判断等待时间,那么直接调用await()方法,这是Condition类的方法
                    if (!timed)
                        trip.await();
                        //如果需要等待的时间大于0,那么线程阻塞要设置时间,超过这个时间需要被唤醒
                    else if (nanos > 0L)
                     //设置超时阻塞时间,线程就被阻塞了,程序执行到这里就不会继续向下执行,直到这个线程被唤醒,才会继续向下执行
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    if (g == generation && ! g.broken) {
                         //当前线程在阻塞过程,被中断了或者由于这一代屏障前的其他线程原因导致的代损坏了
                        //执行breakBarrier方法唤醒其他线程
                        breakBarrier();
                        throw ie;
                    } else {
                        // We're about to finish waiting even if we had not
                        // been interrupted, so this interrupt is deemed to
                        // "belong" to subsequent execution.
                        //如果不是上述两个原因造成的,就中断当前线程
                        Thread.currentThread().interrupt();
                    }
                }
                // 当有任何一个线程中断,会调用 breakBarrier 方法.
                // 就会唤醒其他的线程,其他线程醒来后,也要抛出异常
                //走到这里这名线程被唤醒了,唤醒的方式可能是计数器为零被唤醒,也有可能调用breakBarrier
                //方法导致代坏掉了而唤醒的,那么需要需要抛出代 破损异常
                if (g.broken)
                    throw new BrokenBarrierException();
                // g != generation 正常换代了,因为计数器为零了,代更新了
                //一切正常,返回当前线程所在屏障的下标
                // 如果 g == generation,说明还没有换代,那为什么会醒了?这里就是代的作用
                // 因为一个线程可以使用多个屏障,当别的屏障唤醒了这个线程,就会走到这里,所以需要判断是否是当前代。
                // 正是因为这个原因,才需要 generation 来保证正确。  
                if (g != generation)
                    return index;
            //如果不需要设置等待时间并且传入的时间不合法小于0,那么执行breakBarrier抛出异常
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * Creates a new {@code CyclicBarrier} that will trip when the
     * given number of parties (threads) are waiting upon it, and which
     * will execute the given barrier action when the barrier is tripped,
     * performed by the last thread entering the barrier.
     *
     * @param parties the number of threads that must invoke {@link #await}
     *        before the barrier is tripped
     * @param barrierAction the command to execute when the barrier is
     *        tripped, or {@code null} if there is no action
     * @throws IllegalArgumentException if {@code parties} is less than 1
     */
    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }

    /**
     * Creates a new {@code CyclicBarrier} that will trip when the
     * given number of parties (threads) are waiting upon it, and
     * does not perform a predefined action when the barrier is tripped.
     *
     * @param parties the number of threads that must invoke {@link #await}
     *        before the barrier is tripped
     * @throws IllegalArgumentException if {@code parties} is less than 1
     */
    public CyclicBarrier(int parties) {
        this(parties, null);
    }

    /**
     * Returns the number of parties required to trip this barrier.
     *
     * @return the number of parties required to trip this barrier
     */
    public int getParties() {
        return parties;
    }

    /**
     * Waits until all {@linkplain #getParties parties} have invoked
     * {@code await} on this barrier.
     *
     * <p>If the current thread is not the last to arrive then it is
     * disabled for thread scheduling purposes and lies dormant until
     * one of the following things happens:
     * <ul>
     * <li>The last thread arrives; or
     * <li>Some other thread {@linkplain Thread#interrupt interrupts}
     * the current thread; or
     * <li>Some other thread {@linkplain Thread#interrupt interrupts}
     * one of the other waiting threads; or
     * <li>Some other thread times out while waiting for barrier; or
     * <li>Some other thread invokes {@link #reset} on this barrier.
     * </ul>
     *
     * <p>If the current thread:
     * <ul>
     * <li>has its interrupted status set on entry to this method; or
     * <li>is {@linkplain Thread#interrupt interrupted} while waiting
     * </ul>
     * then {@link InterruptedException} is thrown and the current thread's
     * interrupted status is cleared.
     *
     * <p>If the barrier is {@link #reset} while any thread is waiting,
     * or if the barrier {@linkplain #isBroken is broken} when
     * {@code await} is invoked, or while any thread is waiting, then
     * {@link BrokenBarrierException} is thrown.
     *
     * <p>If any thread is {@linkplain Thread#interrupt interrupted} while waiting,
     * then all other waiting threads will throw
     * {@link BrokenBarrierException} and the barrier is placed in the broken
     * state.
     *
     * <p>If the current thread is the last thread to arrive, and a
     * non-null barrier action was supplied in the constructor, then the
     * current thread runs the action before allowing the other threads to
     * continue.
     * If an exception occurs during the barrier action then that exception
     * will be propagated in the current thread and the barrier is placed in
     * the broken state.
     *
     * @return the arrival index of the current thread, where index
     *         {@code getParties() - 1} indicates the first
     *         to arrive and zero indicates the last to arrive
     * @throws InterruptedException if the current thread was interrupted
     *         while waiting
     * @throws BrokenBarrierException if <em>another</em> thread was
     *         interrupted or timed out while the current thread was
     *         waiting, or the barrier was reset, or the barrier was
     *         broken when {@code await} was called, or the barrier
     *         action (if present) failed due to an exception
     */
    public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }

    /**
     * Waits until all {@linkplain #getParties parties} have invoked
     * {@code await} on this barrier, or the specified waiting time elapses.
     *
     * <p>If the current thread is not the last to arrive then it is
     * disabled for thread scheduling purposes and lies dormant until
     * one of the following things happens:
     * <ul>
     * <li>The last thread arrives; or
     * <li>The specified timeout elapses; or
     * <li>Some other thread {@linkplain Thread#interrupt interrupts}
     * the current thread; or
     * <li>Some other thread {@linkplain Thread#interrupt interrupts}
     * one of the other waiting threads; or
     * <li>Some other thread times out while waiting for barrier; or
     * <li>Some other thread invokes {@link #reset} on this barrier.
     * </ul>
     *
     * <p>If the current thread:
     * <ul>
     * <li>has its interrupted status set on entry to this method; or
     * <li>is {@linkplain Thread#interrupt interrupted} while waiting
     * </ul>
     * then {@link InterruptedException} is thrown and the current thread's
     * interrupted status is cleared.
     *
     * <p>If the specified waiting time elapses then {@link TimeoutException}
     * is thrown. If the time is less than or equal to zero, the
     * method will not wait at all.
     *
     * <p>If the barrier is {@link #reset} while any thread is waiting,
     * or if the barrier {@linkplain #isBroken is broken} when
     * {@code await} is invoked, or while any thread is waiting, then
     * {@link BrokenBarrierException} is thrown.
     *
     * <p>If any thread is {@linkplain Thread#interrupt interrupted} while
     * waiting, then all other waiting threads will throw {@link
     * BrokenBarrierException} and the barrier is placed in the broken
     * state.
     *
     * <p>If the current thread is the last thread to arrive, and a
     * non-null barrier action was supplied in the constructor, then the
     * current thread runs the action before allowing the other threads to
     * continue.
     * If an exception occurs during the barrier action then that exception
     * will be propagated in the current thread and the barrier is placed in
     * the broken state.
     *
     * @param timeout the time to wait for the barrier
     * @param unit the time unit of the timeout parameter
     * @return the arrival index of the current thread, where index
     *         {@code getParties() - 1} indicates the first
     *         to arrive and zero indicates the last to arrive
     * @throws InterruptedException if the current thread was interrupted
     *         while waiting
     * @throws TimeoutException if the specified timeout elapses.
     *         In this case the barrier will be broken.
     * @throws BrokenBarrierException if <em>another</em> thread was
     *         interrupted or timed out while the current thread was
     *         waiting, or the barrier was reset, or the barrier was broken
     *         when {@code await} was called, or the barrier action (if
     *         present) failed due to an exception
     */
    public int await(long timeout, TimeUnit unit)
        throws InterruptedException,
               BrokenBarrierException,
               TimeoutException {
        return dowait(true, unit.toNanos(timeout));
    }

    /**
     * Queries if this barrier is in a broken state.
     *
     * @return {@code true} if one or more parties broke out of this
     *         barrier due to interruption or timeout since
     *         construction or the last reset, or a barrier action
     *         failed due to an exception; {@code false} otherwise.
     */
    public boolean isBroken() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return generation.broken;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Resets the barrier to its initial state.  If any parties are
     * currently waiting at the barrier, they will return with a
     * {@link BrokenBarrierException}. Note that resets <em>after</em>
     * a breakage has occurred for other reasons can be complicated to
     * carry out; threads need to re-synchronize in some other way,
     * and choose one to perform the reset.  It may be preferable to
     * instead create a new barrier for subsequent use.
     重置整个屏障为初始状态,将已经拦截的释放掉,全部重新开始计数
     */
    public void reset() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //执行breakBarrier方法唤醒其他线程
            breakBarrier();   // break the current generation
            //更新屏障的状态并唤醒其他线程
            nextGeneration(); // start a new generation
        } finally {
            lock.unlock();
        }
    }

    /**
     * Returns the number of parties currently waiting at the barrier.
     * This method is primarily useful for debugging and assertions.
     *
     * @return the number of parties currently blocked in {@link #await}
     */
    public int getNumberWaiting() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return parties - count;
        } finally {
            lock.unlock();
        }
    }
}

实例

假如公司团建,大家一起做大巴车,在大巴车出发之前,肯定是需要点名的,只有大家都到车上之后,才会发车,然后到了到了目的地之后,肯定是所有人都下车了,司机才能把车开走,这个过程中涉及了2次大家都就位之后,司机才能继续操作,可以证明CyclicBarrier可以循环使用计数器。

package com.runlion.middleground.marginal;

import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;

/**
 * 
 * @description:
 * @date 2024年04月30日 17:26:05
 */
@Slf4j
public class StudyTest {
    @Getter
    @Setter
    static class Flag{
        public int count=0;
    }
    @Test
    @SneakyThrows
    public void testCyclicBarrier() {
        Flag flag=new Flag();
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> {
            if(flag.getCount()==0){
                System.out.println("所有人都上车了,可以发车了");
                flag.setCount(1);
            }else {
                System.out.println("所有人都下车了,司机可以走了");
            }

        });

        for (int i = 1; i < 6; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + "号上车了");
                    cyclicBarrier.await();
                    System.out.println(Thread.currentThread().getName() + "号开始休息了");
                    TimeUnit.MILLISECONDS.sleep(new Random().nextInt(2000));
                    System.out.println(Thread.currentThread().getName() + "号下车了");
                    cyclicBarrier.await();
                    System.out.println(Thread.currentThread().getName() + "号到达目的地了");
                }catch (Exception e){
                    log.info(e.getMessage());
                }

            }, String.valueOf(i)).start();
        }
        System.out.println("主线程不阻塞");
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1635494.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

什么牌子的洗地机质量比较好?四大热门品牌实测,快看过来

随着科技的发展&#xff0c;各种智能家居产品层出不穷&#xff0c;为我们的生活带来了诸多便利。近年来&#xff0c;在家庭清洁领域&#xff0c;洗地机成为了热门选择&#xff0c;可是什么牌子的洗地机质量比较好呢&#xff1f;我们一起来看看吧。 洗地机选购技巧&#xff1a;…

大数据中的项目数据采集

Datax介绍 官网&#xff1a; DataX/introduction.md at master alibaba/DataX GitHub DataX 是阿里云 DataWorks数据集成 的开源版本&#xff0c;在阿里巴巴集团内被广泛使用的离线数据同步工具/平台。 DataX 实现了包括 MySQL、Oracle、OceanBase、SqlServer、Postgre、HDFS…

【漏洞复现】若依druid 未授权访问漏洞

0x01 产品简介 若依&#xff08;Ruoyi&#xff09;框架是一款基于Spring Boot、Spring Cloud、OAuth2与JWT鉴权等核心技术的快速开发平台。它支持多种安全框架和持久化框架&#xff0c;并采用前后端分离的模式进行开发&#xff0c;具备高度的灵活性和可扩展性。若依框架提供了…

VTK 的可视化方法:颜色映射

VTK 的可视化方法&#xff1a;颜色映射 VTK 的可视化方法&#xff1a;颜色映射颜色映射过程vtkDataSetvtkDataSetAttributesvtkLookUpTablevtkScalarBarActor实例参考 VTK 的可视化方法&#xff1a;颜色映射 颜色映射的操作对象是数据集中的标量属性。它是一种常用的标量算法。…

.net core ef 连表查询

Information和TypeInfo连表查询 类似&#xff1a; select st.Title1,si.* from [Star_Information] si left join Star_TypeInfo st on si.typeId2st.id 先在EfCoreDbContext.cs配置 protected override void OnModelCreating(ModelBuilder builder){base.OnModelCreating(b…

C++数据结构——二叉搜索树

二叉搜索树的概念 二叉树又称二叉排序树(BST&#xff0c;Binary Search Tree)&#xff0c;它是一颗空树&#xff0c;也可以是一颗具有下列性质的二叉树&#xff1a; 1.假如它的左子树不为空&#xff0c;那么左子树上的结点值都小于根结点的值。 2.假如它的右子树不为空&…

K8S controller编写之Informer的原理+使用[drift]

概念 核心思想&#xff08;重点&#xff09;watch-list 机制 Watch 通过 HTTP 协议与 Kubernetes API Server 建立长连接&#xff0c;接收 Kubernetes API Server 发来的资源变更事件。Watch 操作的实现机制使用 HTTP 协议的分块传输编码——当 client-go 调用 Kubernetes API…

nacos(docker部署)+springboot集成

文章目录 说明零nacos容器部署初始化配置高级配置部分访问权限控制命名空间设置新建配置文件 springboot配置nacos添加依赖编写测试controller 说明 nacos容器部署采用1Panel运维面板&#xff0c;进行部署操作&#xff0c;简化操作注意提前安装好1Panel和配置完成docker镜像加…

三、VUE数据代理

一、初识VUE 二、再识VUE-MVVM 三、VUE数据代理 Object.defineProperty() Object.defineProperty() 静态方法会直接在一个对象上定义一个新属性&#xff0c;或修改其现有属性&#xff0c;并返回此对象。 Object.defineProperty() 数据代理 通过一个对象代理另一个对象中属…

CSS 06

精灵图 为什么要使用精灵图 一个网页中往往会应用很多小的背景图像作为修饰&#xff0c;当网页中的图像过多时&#xff0c;服务器就会频繁地接收和发送请求图片&#xff0c;造成服务器请求压力过大&#xff0c;这将大大降低页面的加载速度,因此&#xff0c;为了有效地减少服务…

Python来计算 1,2,3,4 能组成多少个不相同且不重复的三位数?

我们今天的例子是 有 1&#xff0c;2&#xff0c;3&#xff0c;4 四个数字&#xff0c;它们能组成多省个互不相同且无重复的三位数&#xff1f;都分别是多少&#xff1f; 话不多说&#xff0c;我们先上代码 num 0 # 我们写了三个for循环&#xff0c;表示生成的三位数 for i…

YOLOv5模型训练处理自己数据集(标签统计、数据集划分、数据增强)

上一节中我们讲到如何使用Labelimg工具标注自己的数据集&#xff0c;链接&#xff1a;YOLOv5利用Labelimg标注自己数据集&#xff0c;完成1658张数据集的预处理&#xff0c;接下来将进一步处理这批数据&#xff0c;通常是先划分再做数据增强。 目录 一、统计txt文件各标签类型…

在项目中添加日志功能-Python logging模块新手入门

Python Logging 日志模块新手入门 这也是规划里的一篇工具文章&#xff0c;在写项目代码的时候不但要考虑代码的架构代码的后期维护和调试等也是一个比较关键的问题&#xff0c;之前写代码的时候日志这块的代码直接是任务驱动简单搜了一下就用了&#xff0c;但是秉持着打好基础…

十八、Java解析XML文件

1、XML文档语法和DTD约束 1)XML定义 XML即可扩展的标记语言,可以定义语义标记(标签),是元标记语言。XML不像超文本标记语言HTML,HTML只能使用规定的标记,对于XML,用户可以定义自己需要的标记。 XML(Extensible Markup Language)和HTML(Hyper Text Markup Language)师出同…

智能体可靠性的革命性提升,揭秘知识工程领域的参考架构新篇章

引言&#xff1a;知识工程的演变与重要性 知识工程&#xff08;Knowledge Engineering&#xff0c;KE&#xff09;是一个涉及激发、捕获、概念化和形式化知识以用于信息系统的过程。自计算机科学和人工智能&#xff08;AI&#xff09;历史以来&#xff0c;知识工程的工作流程因…

救护员证学习笔记

第一节 红十字运动基础知识 红十字运动的优势 197个主权国家、191个红十字会 四次获得诺贝尔和平奖 红十字运动的组成 红十字运动七项准则 红十字运动的标志 新中国红十字运动宗旨 保护人的生命与健康 维护人的尊严 发扬人道主义精神 促进和平事业进步 红十字会的主要工作 …

VGG16简单部署(使用自己的数据集)

一.注意事项 1.本文主要是引用大佬的文章&#xff08;侵权请联系&#xff0c;马上删除&#xff09;&#xff0c;做的工作为简单补充 二.介绍 ①简介&#xff1a;VGG16是一种卷积神经网络模型&#xff0c;由牛津大学视觉几何组&#xff08;Visual Geometry Group&#xff09;开…

【错题集-编程题】组队竞赛(排序 + 贪心)

牛客对应题目链接&#xff1a;组队竞赛_牛客笔试题_牛客网 (nowcoder.com) 一、分析题目 运用 贪心 思想&#xff1a; 先将数组排好序。总和最大 -> 每个小组的分数尽可能大。最大的数拿不到&#xff0c;只能退而求其次拿到倒数第⼆个⼈的分数&#xff0c;再补上一个小的数…

shell脚本-监控系统内存和磁盘容量

监控内存和磁盘容量除了可以使用zabbix监控工具来监控&#xff0c;还可以通过编写Shell脚本来监控。 #! /bin/bash #此脚本用于监控内存和磁盘容量&#xff0c;内存小于500MB且磁盘容量小于1000MB时报警#提取根分区剩余空间 disk_size$(df / | awk /\//{print $4})#提取内存剩…

西圣发布全新磁吸无线充电宝:打破传统,让充电更加高效、便捷

手机作为日常生活中最不能离开的数码单品之一&#xff0c;出门在外&#xff0c;电量情况总是让人担忧&#xff0c;一款靠谱的移动电源简直就是救星&#xff01;近日&#xff0c;西圣品牌推出了一款集高效、安全、便携于一体的无线充电宝——西圣PB无线磁吸充电宝&#xff0c;以…