看图学源码之 Atomic 类源码浅析二(cas + 分治思想的原子累加器)

原子累加器

相较于上一节看图学源码 之 Atomic 类源码浅析一(cas + 自旋操作的 AtomicXXX原子类)说的的原子类,原子累加器的效率会更高

在这里插入图片描述

XXXXAdderXXXAccumulator 区别就是 Adder只有add 方法,Accumulator是可以进行自定义运算方法的

始于 Striped64
abstract class Striped64 extends Number {// cpu 运行核数, 控制数组的大小static final int NCPU = Runtime.getRuntime().availableProcessors();// 当非空时,大小是 2 的幂。 transient volatile Cell[] cells;// 表初始化竞争期间的后备值  通过  CAS 更新  就是 valuetransient volatile long base;// 锁的 标志位 调整单元大小和/或创建单元时使用自旋锁(通过 CAS 锁定)transient volatile int cellsBusy;//Base的cas 操作final boolean casBase(long cmp, long val) {return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);}// CellsBusy的cas操作final boolean casCellsBusy() {return UNSAFE.compareAndSwapInt(this, CELLSBUSY, 0, 1);}// 主要是给不同的线程找到数组中不同的下标// 当前线程的探测值static final int getProbe() {return UNSAFE.getInt(Thread.currentThread(), PROBE);}// 给定线程的给定探测值static final int advanceProbe(int probe) {probe ^= probe << 13;   // xorshiftprobe ^= probe >>> 17;probe ^= probe << 5;UNSAFE.putInt(Thread.currentThread(), PROBE, probe);return probe;}......// Unsafe mechanics// Unsafe 的获取 和 偏移量的获取private static final sun.misc.Unsafe UNSAFE;private static final long BASE;private static final long CELLSBUSY;private static final long PROBE;static {try {UNSAFE = sun.misc.Unsafe.getUnsafe();Class<?> sk = Striped64.class;BASE = UNSAFE.objectFieldOffset(sk.getDeclaredField("base"));CELLSBUSY = UNSAFE.objectFieldOffset(sk.getDeclaredField("cellsBusy"));Class<?> tk = Thread.class;PROBE = UNSAFE.objectFieldOffset(tk.getDeclaredField("threadLocalRandomProbe"));} catch (Exception e) {throw new Error(e);}}}
内部类

@sun.misc.Contended——解决伪共享,进行字节填充

@sun.misc.Contended static final class Cell {// 操作的数volatile long value;// 构造器Cell(long x) { value = x; }// 进行 cas final boolean cas(long cmp, long val) {return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);}
}
LongAdder

可能会出现一边正在进行 累加操作,一边又在执行求和操作,所以就导致了不是 强一致性,而是最终一致性

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

public class LongAdder extends Striped64 implements Serializable {private static final long serialVersionUID = 7249069246863182397L;public LongAdder() {}public void add(long x) {Cell[] as; long b, v; int m; Cell a;// 数组是不是 null(判断有没有发生竞争,因为只有竞争发生才会初始化数组)// 没有初始化(就是没有竞争) 直接对 base 的值+x 失败if ((as = cells) != null || !casBase(b = base, b + x)) {//有竞争的时候boolean uncontended = true;// 数组还是没有初始化 || 数组初始化,但是数组的长度  < 0 || 数组中的该位置的值是 null (表示这个下标没有初始化)|| cas的方式把当前位置的值 + x ,cas 失败if (as == null || (m = as.length - 1) < 0 ||(a = as[getProbe() & m]) == null ||!(uncontended = a.cas(v = a.value, v + x)))// 发生冲突都会走这里longAccumulate(x, null, uncontended);}}public void increment() {add(1L);}public void decrement() {add(-1L);}// 返回当前总和。返回的值不是原子快照;// 在没有并发更新的情况下调用会返回准确的结果,但计算总和时发生的并发更新可能不会被合并// 所以不是 强一致性的public long sum() {Cell[] as = cells; Cell a;long sum = base;//数组不是 nullif (as != null) {//遍历数组,for (int i = 0; i < as.length; ++i) {//数组中的槽位不是 null,对槽位的数据进行运算,赋值加到base中if ((a = as[i]) != null)sum += a.value;}}//返回总的值return sum;}// 将保持总和为零的变量重置。此方法可能是创建新加法器的有用替代方法,但仅在没有并发更新时才有效。// 由于此方法本质上是活泼的,因此仅应在  已知没有线程同时更新时才使用它。public void reset() {Cell[] as = cells; Cell a;base = 0L;if (as != null) {// 数组存在,遍历数组,将数组中所有的值设置为 0 for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)a.value = 0L;}}}// 相当于sum后跟reset // 该方法可以应用于例如 多线程计算之间的静止点期间。// 如果此方法同时有更新,则不能保证返回值是重置之前发生的最终值。public long sumThenReset() {Cell[] as = cells; Cell a;long sum = base;base = 0L;if (as != null) {// 数组存在,遍历数组,先求和  后把数组中所有的值设置为 0 for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null) {sum += a.value;a.value = 0L;}}}return sum;}// 返回sum的字符串表示形式public String toString() {return Long.toString(sum());}// 返回sumpublic long longValue() {return sum();}//缩小基元转换后以 int 形式返回sum public int intValue() {return (int)sum();}// 加宽基元转换后以float形式返回sum public float floatValue() {return (float)sum();}//加宽基元转换后以 double 形式返回sum public double doubleValue() {return (double)sum();}private static class SerializationProxy implements Serializable {private static final long serialVersionUID = 7249069246863182397L;private final long value;// sum() 返回的当前值。SerializationProxy(LongAdder a) {value = a.sum();}// 返回一个LongAdder对象,其初始状态由该代理保存。private Object readResolve() {LongAdder a = new LongAdder();a.base = value;return a;}}private Object writeReplace() {return new SerializationProxy(this);}private void readObject(java.io.ObjectInputStream s)throws java.io.InvalidObjectException {throw new java.io.InvalidObjectException("Proxy required");}}
striped64中的 longAccumulate

在这里插入图片描述

final void longAccumulate(long x, LongBinaryOperator fn,boolean wasUncontended) {int h;// 拿 hash 值,拿不到强制获取if ((h = getProbe()) == 0) {ThreadLocalRandom.current(); // force initializationh = getProbe();// 将 wasUncontended 的值设为 true,表示当前线程是未争用的。wasUncontended = true;}boolean collide = false;                // True if last slot nonemptyfor (;;) {Cell[] as; Cell a; int n; long v;// 分支 1//数组已经初始化,出现了竞争if ((as = cells) != null && (n = as.length) > 0) {// 分支1.1// 当前位置的值是nullif ((a = as[(n - 1) & h]) == null) {// 锁的标志位 == 0 ,没有加锁 if (cellsBusy == 0) {       // Try to attach new CellCell r = new Cell(x);   // Optimistically create// 加锁if (cellsBusy == 0 && casCellsBusy()) {boolean created = false;   try {               // Recheck under lockCell[] rs; int m, j;//  加锁之后再次检查指定位置是否为空 //  数组初始化过了 && 当前位置的值不是nullif ((rs = cells) != null &&(m = rs.length) > 0 &&rs[j = (m - 1) & h] == null) {// 给数组的指定位置设置为  之前设置过的cell对象rs[j] = r;// 创建成功created = true;}} finally {// 解锁cellsBusy = 0;}if (created)break;continue;           // Slot is now non-empty}}// 有人加锁了,发生了冲突 避免在当前位置发生碰撞的情况下继续进行操作,将 collide 标志位设置为 false。collide = false;}// 分支1.2// 没有发生竞争else if (!wasUncontended)       // CAS already known to fail// 此时是发生了竞争wasUncontended = true;      // Continue after rehash// 分支1.3// cas 的方式更新 此位置的值, cas 失败表示有线程正在此位置执行操作else if (a.cas(v = a.value, ((fn == null) ? v + x :fn.applyAsLong(v, x))))break;// 分支1.4// n > cpu 的个数  当前分段数组的长度是否已经达到或超过了处理器的数量。//如果是,说明分段数组已经达到了最大的容量或者已经很大了,不再需要继续进行扩容操作。// 或者 cells  发生了变化,当前线程获取到的分段数组引用是否与共享变量中的引用相等。// 如果不相等,说明在当前线程获取到分段数组的过程中,有其他线程进行了修改,即分段数组已经发生了变化。else if (n >= NCPU || cells != as)collide = false;            // At max size or stale// 分支1.5// 此时是发生了碰撞的 collide 被设置为 true else if (!collide)collide = true;// 分支1.6 // 扩容// 没有被锁 && cas  的方式 成功加锁 else if (cellsBusy == 0 && casCellsBusy()) {try {// 数组没有变化if (cells == as) {      // Expand table unless stale// as 数组长度扩大一倍Cell[] rs = new Cell[n << 1];// 元素直接赋值for (int i = 0; i < n; ++i)rs[i] = as[i];cells = rs;}} finally {// 解锁cellsBusy = 0;}//没有冲突collide = false;//扩容成功,继续循环continue;                   // Retry with expanded table}// 更新 hash 值h = advanceProbe(h);}
// 分支 2// 此处数组没有进行初始化,此时进行初始化// 锁的标志为 == 0  &&  数组没有改变(多线程情况下该线程没有被其他线程初始化)  && cas 成功的把锁的标志位 设置为 1(加锁流程)// 当前的 cells 数组没有被其他线程占用,并且成功获取了 cellsBusy 锁else if (cellsBusy == 0 && cells == as && casCellsBusy()) {boolean init = false;try {                           // Initialize table// 加完锁之后再次判断一次  cells 数组没有发生过变化if (cells == as) { // 数组 长度默认为2Cell[] rs = new Cell[2];// 给rs 赋值为 要加入的 xrs[h & 1] = new Cell(x);// 将 cells 数组变更为 rscells = rs;// 初始化成功init = true;}} finally {// 解锁cellsBusy = 0;}if (init)    //初始化成功break;    // 退出循环}
// 分支 3  cas 的方式 操作 base  , fn 函数式接口的方法  == null 默认加法,否则就是定义的方法else if (casBase(v = base, ((fn == null) ? v + x : fn.applyAsLong(v, x))))break;                          // Fall back on using base  退出循环}
}
LongAccumulator

在这里插入图片描述

在这里插入图片描述

image-20231207001843693

public class LongAccumulator extends Striped64 implements Serializable {private static final long serialVersionUID = 7249069246863182397L;private final LongBinaryOperator function;private final long identity;public LongAccumulator(LongBinaryOperator accumulatorFunction,long identity) {this.function = accumulatorFunction;base = this.identity = identity;}// 更新值public void accumulate(long x) {Cell[] as; long b, v, r; int m; Cell a;// 有竞争  || ( cas 运算 base 的值成功  && 对 base进行cas更新失败 )if ((as = cells) != null ||//  function.applyAsLong  函数式接口 (r = function.applyAsLong(b = base, x)) != b && !casBase(b, r)) {boolean uncontended = true;// 出现了竞争// 数组还是没有初始化 || 数组初始化,但数组的长度 < 0 || 数组中的该位置的值是 null (表示这个下标没有初始化)|| (cas的方式运算当前位置的值 失败 && cas 更新当前位置的值也失败)if (as == null || (m = as.length - 1) < 0 ||(a = as[getProbe() & m]) == null ||!(uncontended =(r = function.applyAsLong(v = a.value, x)) == v ||a.cas(v, r)))longAccumulate(x, function, uncontended);}}// 返回当前值。返回的值不是原子快照;// 在没有并发更新的情况下调用会返回准确的结果,但计算值时发生的并发更新可能不会被合并。public long get() {Cell[] as = cells; Cell a;long result = base;// 数组存在if (as != null) {// 遍历数组for (int i = 0; i < as.length; ++i) {//数组中的槽位不是 nullif ((a = as[i]) != null)//对槽位的数据进行运算,赋值加到base中result = function.applyAsLong(result, a.value);}}// 并返回总的值return result;}// 重置变量以维护对标识值的更新。// 此方法可能是创建新更新程序的有用替代方法,但仅在没有并发更新时才有效。// 由于此方法本质上是活泼的,因此仅应在已知没有线程同时更新时才使用它。 public void reset() {Cell[] as = cells; Cell a;base = identity;// 数组存在if (as != null) {// 遍历数组for (int i = 0; i < as.length; ++i) {//数组中的槽位不是 nullif ((a = as[i]) != null)//将槽位的值设置为  identitya.value = identity;}}}// 效果相当于get后面跟着reset 。// 该方法可以应用于例如多线程计算之间的静止点期间。// 如果此方法同时有更新,则不能保证返回值是重置之前发生的最终值。public long getThenReset() {Cell[] as = cells; Cell a;long result = base;base = identity;// 数组存在if (as != null) {// 遍历数组for (int i = 0; i < as.length; ++i) {//数组中的槽位不是 nullif ((a = as[i]) != null) {// 将槽位的值设置为  identity// 对槽位的数据进行运算,赋值加到base中long v = a.value;a.value = identity;result = function.applyAsLong(result, v);}}}return result;}public String toString() {return Long.toString(get());}public long longValue() {return get();}public int intValue() {return (int)get();}public float floatValue() {return (float)get();}public double doubleValue() {return (double)get();}/*** 序列化代理,用于避免以序列化形式引用非公共 Striped64 超类*/private static class SerializationProxy implements Serializable {private static final long serialVersionUID = 7249069246863182397L;private final long value;private final LongBinaryOperator function;private final long identity;SerializationProxy(LongAccumulator a) {function = a.function;identity = a.identity;value = a.get();}private Object readResolve() {LongAccumulator a = new LongAccumulator(function, identity);a.base = value;return a;}}private Object writeReplace() {return new SerializationProxy(this);}private void readObject(java.io.ObjectInputStream s)throws java.io.InvalidObjectException {throw new java.io.InvalidObjectException("Proxy required");}}

在Double 中会有 doubleToRawLongBits的操作,主要是检查数组越界的

DoubleAdder

在这里插入图片描述

public class DoubleAdder extends Striped64 implements Serializable {private static final long serialVersionUID = 7249069246863182397L;/*
请注意,我们必须使用“long”作为底层表示,因为 double 没有compareAndSet,因为任何 CAS 实现中使用的按位等于与双精度等于不同
然而,我们仅使用 CAS 来检测和缓解争用,无论如何,按位等于效果最好。
原则上,这里使用的 longdouble 转换在大多数平台上基本上应该是免费的,因为它们只是重新解释位。*/public DoubleAdder() {}public void add(double x) {Cell[] as; long b, v; int m; Cell a;// 数组存在 || 对 base 进行 cas运算操作失败  
if ((as = cells) != null ||!casBase(b = base,Double.doubleToRawLongBits(Double.longBitsToDouble(b) + x))) {// boolean uncontended = true;// 数组为 空 || 数组的长度 < 0  || 当前位置的值为 null  ||  对该位置的值进行cas 运算失败if (as == null || (m = as.length - 1) < 0 ||(a = as[getProbe() & m]) == null ||!(uncontended = a.cas(v = a.value,Double.doubleToRawLongBits(Double.longBitsToDouble(v) + x))))doubleAccumulate(x, null, uncontended);}}/**返回当前总和。返回的值不是原子快照;在没有并发更新的情况下调用会返回准确的结果,但计算总和时发生的并发更新可能不会被合并。由于浮点算术不是严格关联的,因此返回的结果不需要与在单个变量的一系列连续更新中获得的值相同。*/public double sum() {Cell[] as = cells; Cell a;double sum = Double.longBitsToDouble(base);if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)sum += Double.longBitsToDouble(a.value);}}return sum;}/**将保持总和为零的变量重置。此方法可能是创建新加法器的有用替代方法,但仅在没有并发更新时才有效。由于此方法本质上是活泼的,因此仅应在已知没有线程同时更新时才使用它。*/public void reset() {Cell[] as = cells; Cell a;base = 0L; // relies on fact that double 0 must have same rep as longif (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)a.value = 0L;}}}/**相当于sum后跟reset 。该方法可以应用于例如多线程计算之间的静止点期间。如果此方法同时有更新,则不能保证返回值是重置之前发生的最终值。*/public double sumThenReset() {Cell[] as = cells; Cell a;double sum = Double.longBitsToDouble(base);base = 0L;if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null) {long v = a.value;a.value = 0L;sum += Double.longBitsToDouble(v);}}}return sum;}public String toString() {return Double.toString(sum());}public double doubleValue() {return sum();}public long longValue() {return (long)sum();}public int intValue() {return (int)sum();}public float floatValue() {return (float)sum();}private static class SerializationProxy implements Serializable {private static final long serialVersionUID = 7249069246863182397L;private final double value;SerializationProxy(DoubleAdder a) {value = a.sum();}private Object readResolve() {DoubleAdder a = new DoubleAdder();a.base = Double.doubleToRawLongBits(value);return a;}}private Object writeReplace() {return new SerializationProxy(this);}private void readObject(java.io.ObjectInputStream s)throws java.io.InvalidObjectException {throw new java.io.InvalidObjectException("Proxy required");}}
striped64中的 doubleAccumulate

和上面的 striped64中的 longAccumulate 几乎一模一样,只有doubleToRawLongBits部分的细微差别

final void doubleAccumulate(double x, DoubleBinaryOperator fn,boolean wasUncontended) {int h;// 拿 hash 值,拿不到强制获取if ((h = getProbe()) == 0) {ThreadLocalRandom.current(); // force initializationh = getProbe();wasUncontended = true;}boolean collide = false;                // True if last slot nonemptyfor (;;) {Cell[] as; Cell a; int n; long v;
// 分支 1//数组已经初始化,出现了竞争if ((as = cells) != null && (n = as.length) > 0) {// 分支1.1// 当前位置的值是nullif ((a = as[(n - 1) & h]) == null) {// 锁的标志位 == 0 ,没有加锁 if (cellsBusy == 0) {       // Try to attach new CellCell r = new Cell(Double.doubleToRawLongBits(x));// 加锁if (cellsBusy == 0 && casCellsBusy()) {boolean created = false;try {               // Recheck under lockCell[] rs; int m, j;// 数组初始化过了 && 当前位置的值不是nullif ((rs = cells) != null &&(m = rs.length) > 0 &&rs[j = (m - 1) & h] == null) {// 给数组的位置设置为  之前设置过的cell对象rs[j] = r;// 创建成功created = true;}} finally {// 解锁cellsBusy = 0;}if (created)break;continue;           // Slot is now non-empty}}// 有人加锁了,发生了冲突collide = false;}// 分支1.2// 没有发生竞争else if (!wasUncontended)       // CAS already known to fail// 此时是发生了竞争wasUncontended = true;      // Continue after rehash// 分支1.3// cas 的方式更新 此位置的值, cas 失败表示有线程正在此位置执行操作else if (a.cas(v = a.value,((fn == null) ?Double.doubleToRawLongBits(Double.longBitsToDouble(v) + x) :Double.doubleToRawLongBits(fn.applyAsDouble(Double.longBitsToDouble(v), x)))))break;// 分支1.4// n > cpu 的个数 或者 cells  发生了变化,表示 之前没有发生碰撞,不能扩容else if (n >= NCPU || cells != as)collide = false;            // At max size or stale// 分支1.5// 此时是发生了碰撞的 collide 被设置为 true else if (!collide)collide = true;// 分支1.6 // 扩容// 没有被锁 && 成功加锁 else if (cellsBusy == 0 && casCellsBusy()) {try {// 数组没有变化if (cells == as) {      // Expand table unless stale// as 数组长度扩大一倍Cell[] rs = new Cell[n << 1];// 元素直接赋值for (int i = 0; i < n; ++i)rs[i] = as[i];cells = rs;}} finally {// 解锁cellsBusy = 0;}//没有冲突collide = false;//扩容成功,继续循环continue;                   // Retry with expanded table}// 获取hash 值h = advanceProbe(h);}
// 分支 2// 此处数组没有进行初始化,此时进行初始化// 锁的标志为 == 0  &&  数组没有改变(多线程情况下该线程没有被其他线程初始化)  && cas 成功的把锁的标志位 设置为 1(枷锁流程)else if (cellsBusy == 0 && cells == as && casCellsBusy()) {boolean init = false;try {   // Initialize table// 加完锁之后再次判断一次  cells 数组没有发生过变化if (cells == as) {// 数组 长度默认为2Cell[] rs = new Cell[2];// 给rs 赋值为 要加入的 xrs[h & 1] = new Cell(Double.doubleToRawLongBits(x));// 将 cells 数组变更为 rscells = rs;// 初始化成功init = true;}} finally {// 解锁cellsBusy = 0;}if (init)// 初始化成功break;    // 退出循环}
// 分支 3  cas 的方式 操作 base  , fn 函数式接口的方法  == null 默认加法,否则就是定义的方法else if (casBase(v = base,((fn == null) ?Double.doubleToRawLongBits(Double.longBitsToDouble(v) + x) :Double.doubleToRawLongBits(fn.applyAsDouble(Double.longBitsToDouble(v), x)))))break;                          // Fall back on using base// 退出循环}
}
DoubleAccumulator

在这里插入图片描述

public class DoubleAccumulator extends Striped64 implements Serializable {private static final long serialVersionUID = 7249069246863182397L;private final DoubleBinaryOperator function;private final long identity; // use long representationpublic DoubleAccumulator(DoubleBinaryOperator accumulatorFunction,double identity) {this.function = accumulatorFunction;base = this.identity = Double.doubleToRawLongBits(identity);}public void accumulate(double x) {Cell[] as; long b, v, r; int m; Cell a;// 数组存在 || (对 base 进行 cas的 运算操作成功  && 对base 进行cas 更新操作失败  )if ((as = cells) != null ||(r = Double.doubleToRawLongBits(function.applyAsDouble (Double.longBitsToDouble(b = base), x))) != b  && !casBase(b, r)) {boolean uncontended = true;
// 数组为 空 || 数组被初始化但是 数组的长度 < 0  || 当前位置的值为 null  ||  (对该位置的值进行cas 运算失败  || 对该值进行cas 更新失败)if (as == null || (m = as.length - 1) < 0 ||(a = as[getProbe() & m]) == null ||!(uncontended =(r = Double.doubleToRawLongBits(function.applyAsDouble(Double.longBitsToDouble(v = a.value), x))) == v ||a.cas(v, r)))doubleAccumulate(x, function, uncontended);}}public double get() {Cell[] as = cells; Cell a;double result = Double.longBitsToDouble(base);if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)result = function.applyAsDouble(result, Double.longBitsToDouble(a.value));}}return result;}public void reset() {Cell[] as = cells; Cell a;base = identity;if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)a.value = identity;}}}public double getThenReset() {Cell[] as = cells; Cell a;double result = Double.longBitsToDouble(base);base = identity;if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null) {double v = Double.longBitsToDouble(a.value);a.value = identity;result = function.applyAsDouble(result, v);}}}return result;}public String toString() {return Double.toString(get());}public double doubleValue() {return get();}public long longValue() {return (long)get();}public int intValue() {return (int)get();}public float floatValue() {return (float)get();}private static class SerializationProxy implements Serializable {private static final long serialVersionUID = 7249069246863182397L;private final double value;private final DoubleBinaryOperator function;private final long identity;SerializationProxy(DoubleAccumulator a) {function = a.function;identity = a.identity;value = a.get();}private Object readResolve() {double d = Double.longBitsToDouble(identity);DoubleAccumulator a = new DoubleAccumulator(function, d);a.base = Double.doubleToRawLongBits(value);return a;}}private Object writeReplace() {return new SerializationProxy(this);}private void readObject(java.io.ObjectInputStream s)throws java.io.InvalidObjectException {throw new java.io.InvalidObjectException("Proxy required");}}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构和算法】到达首都的最少油耗

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、题目描述 二、题解 三、代码 四、复杂度分析 前言 这是力扣的2477题&#xff0c;难度为中等&#xff0c;解题方案有很多种&…

uc_16_UDP协议_HTTP协议

1 UDP协议 适合游戏、视频等情景&#xff0c;安全性要求不高&#xff0c;效率要求高。 1&#xff09;UDP不提供客户机与服务器的链接&#xff1a; UDP的客户机与服务器不必存在长期关系。一个UDP的客户机在通过一个套接字向一个UDP服务器发送了一个数据报之后&#xff0c;马上…

【flink番外篇】1、flink的23种常用算子介绍及详细示例(完整版)

Flink 系列文章 一、Flink 专栏 Flink 专栏系统介绍某一知识点&#xff0c;并辅以具体的示例进行说明。 1、Flink 部署系列 本部分介绍Flink的部署、配置相关基础内容。 2、Flink基础系列 本部分介绍Flink 的基础部分&#xff0c;比如术语、架构、编程模型、编程指南、基本的…

Docker部署.NET6项目

Docker的三大核心概念 1、docker仓库&#xff08;repository&#xff09; docker仓库&#xff08;repository&#xff09;类似于代码库&#xff0c;是docker集中存放镜像的场所。实际上&#xff0c;注册服务器是存放仓库的地方&#xff0c;其上往往存放着很多仓库。每个仓库集…

Android引用SDK包实现高德地图展示

一、准备工作 注册高德地图开放平台 注册过程我就不多说了&#xff0c;挺简单的&#xff0c;需要登录&#xff0c;然后注册成为开发者&#xff0c;还需要支付宝认证、手机号码验证、邮箱验证挺多的&#xff0c;但是速度很快。基本上随时验证随时注册成功。新建应用新建…

基于单片机智能病床呼叫系统设计

**单片机设计介绍&#xff0c;基于单片机智能病床呼叫系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的智能病床呼叫系统是一种利用单片机技术设计的医疗设备&#xff0c;它能够帮助病人在住院期间快速、方便…

使用pyscenedetect进行视频场景切割

1. 简介 在视频剪辑有转场一词&#xff1a;一个视频场景转换到另一个视频场景&#xff0c;场景与场景之间的过渡或转换&#xff0c;就叫做转场。 本篇介绍一个强大的开源工具PySceneDetect&#xff0c;它是一款基于opencv的视频场景切换检测和分析工具&#xff0c;项目地址: h…

在做题中学习(31):电话号码的字母组合(全排列)

17. 电话号码的字母组合 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;既然要排列组合&#xff0c;就得先根据数字字符取出来 所以先定义一个string类的数组通过下标取到每个数字对应的映射。 string _numsTostr[10]{"","","abc"…

Android:java.lang.RuntimeException: Unable to start activity ComponentInfo

java.lang.RuntimeException: Unable to start activity ComponentInfo 报错描述&#xff1a; 在导入别人项目运行时出现了这个报错&#xff1a; java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.news/com.example.activity.DetailNews}: ja…

消息队列使用指南

介绍 消息队列是一种常用的应用程序间通信方法&#xff0c;可以用来在不同应用程序或组件之间传递数据或消息。消息队列就像一个缓冲区&#xff0c;接收来自发送方的消息&#xff0c;并存储在队列中&#xff0c;等待接收方从队列中取出并处理。 在分布式系统中&#xff0c;消…

PAD平板签约投屏-高端活动的选择

传统的现场纸质签约仪式除了缺乏仪式感之外还缺少互动性&#xff0c;如果要将签约的过程投放到大屏幕上更是需要额外的硬件设备成本。相比于传统的纸质签约仪式&#xff0c;平板现场电子签约的形式更加的新颖、更富有科技感、更具有仪式感。 平板签约投屏是应用于会议签字仪式的…

【面试经典150 | 二叉树】翻转二叉树

文章目录 写在前面Tag题目来源题目解读解题思路方法一&#xff1a;递归方法二&#xff1a;迭代 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于本题…

笔记69:Conv1d 和 Conv2d 之间的区别

笔记地址&#xff1a;D:\work_file\&#xff08;4&#xff09;DeepLearning_Learning\03_个人笔记\4. Transformer 网络变体 a a a a a a a a a a a

万户 ezOFFICE convertFile 文件读取漏洞复现

0x01 产品简介 万户OA ezoffice是万户网络协同办公产品多年来一直将主要精力致力于中高端市场的一款OA协同办公软件产品,统一的基础管理平台,实现用户数据统一管理、权限统一分配、身份统一认证。统一规划门户网站群和协同办公平台,将外网信息维护、客户服务、互动交流和日…

电脑搜不自己的手机热点,其余热点均可!

一、现象&#xff1a; 之前可正常连接&#xff0c;突然间发现收不到自己的WiFi信号&#xff0c;其余人均可收到。通过重复手机电脑关机、改变热点设置中的频段等方式均没解决&#xff0c;同事电脑和手机可搜索到我的WiFi。 二、问题&#xff1a; WiF驱动程序更新 三&#x…

【Docker】Docker Compose,yml 配置指令参考的详细讲解

作者简介&#xff1a; 辭七七&#xff0c;目前大二&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a; 七七的闲谈 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f…

delphi android打开外部文件,报错android.os.FileUriExposedException解决方法

Android 7.0强制启用了被称作 StrictMode的策略&#xff0c;带来的影响就是你的App对外无法暴露file://类型的URI了。 如果你使用Intent携带这样的URI去打开外部App(比如&#xff1a;打开系统相机拍照)&#xff0c;那么会抛出FileUriExposedException异常。 Delphi 为Android…

用Java实现一对一聊天

目录 服务端 客户端 服务端 package 一对一用户; import java.awt.BorderLayout; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; imp…

C# 静态构造函数与类的初始化

静态构造函数&#xff1a; 基本概念&#xff1a; 静态构造函数用于初始化任何静态数据。 静态构造函数的常见特性&#xff1a; 静态构造函数不使用访问修饰符或不具有参数。因为静态构造函数由系统调用&#xff0c;无法人为调用&#xff0c;所以就不存在public、private等。…

【开源】基于Vue和SpringBoot的在线课程教学系统

项目编号&#xff1a; S 014 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S014&#xff0c;文末获取源码。} 项目编号&#xff1a;S014&#xff0c;文末获取源码。 目录 一、摘要1.1 系统介绍1.2 项目录屏 二、研究内容2.1 课程类型管理模块2.2 课程管理模块2…