Atomic原子类:为基本类型的封装类Boolean,Integer,Long,对象引用等提供原子操作.

一、Atomic包下的所有类如下表:

类摘要
AtomicBoolean 可以用原子方式更新的 boolean 值。
AtomicInteger 可以用原子方式更新的 int 值。
AtomicIntegerArray 可以用原子方式更新其元素的 int 数组。
AtomicIntegerFieldUpdater<T> 基于反射的实用工具,可以对指定类的指定 volatile int 字段进行原子更新。
AtomicLong 可以用原子方式更新的 long 值。
AtomicLongArray 可以用原子方式更新其元素的 long 数组。
AtomicLongFieldUpdater<T> 基于反射的实用工具,可以对指定类的指定 volatile long 字段进行原子更新。
AtomicMarkableReference<V> AtomicMarkableReference 维护带有标记位的对象引用,可以原子方式对其进行更新。
AtomicReference<V> 可以用原子方式更新的对象引用。
AtomicReferenceArray<E> 可以用原子方式更新其元素的对象引用数组。
AtomicReferenceFieldUpdater<T,V> 基于反射的实用工具,可以对指定类的指定 volatile 字段进行原子更新。
AtomicStampedReference<V> AtomicStampedReference 维护带有整数“标志”的对象引用,可以用原子方式对其进行更新。

二、AtomicInteger源码分析和基本的方法使用:

Atomicinteger类中的方法列表:

构造方法摘要
AtomicInteger() 
          创建具有初始值 0 的新 AtomicInteger。
AtomicInteger(int initialValue) 
          创建具有给定初始值的新 AtomicInteger。
方法摘要
 int addAndGet(int delta) 
          以原子方式将给定值与当前值相加。
 boolean compareAndSet(int expect, int update) 
          如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
 int decrementAndGet() 
          以原子方式将当前值减 1。
 double doubleValue() 
          以 double 形式返回指定的数值。
 float floatValue() 
          以 float 形式返回指定的数值。
 int get() 
          获取当前值。
 int getAndAdd(int delta) 
          以原子方式将给定值与当前值相加。
 int getAndDecrement() 
          以原子方式将当前值减 1。
 int getAndIncrement() 
          以原子方式将当前值加 1。
 int getAndSet(int newValue) 
          以原子方式设置为给定值,并返回旧值。
 int incrementAndGet() 
          以原子方式将当前值加 1。
 int intValue() 
          以 int 形式返回指定的数值。
 void lazySet(int newValue) 
          最后设置为给定值。
 long longValue() 
          以 long 形式返回指定的数值。
 void set(int newValue) 
          设置为给定值。
 String toString() 
          返回当前值的字符串表示形式。
 boolean weakCompareAndSet(int expect, int update) 
          如果当前值 == 预期值,则以原子方式将该设置为给定的更新值。
从类 java.lang.Number 继承的方法
byteValue, shortValue
从类 java.lang.Object 继承的方法
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait

AtomicInteger源码:

/** ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.*********************//******* Written by Doug Lea with assistance from members of JCP JSR-166* Expert Group and released to the public domain, as explained at* http://creativecommons.org/publicdomain/zero/1.0/*/package java.util.concurrent.atomic;
import sun.misc.Unsafe;/*** An {@code int} value that may be updated atomically.  See the* {@link java.util.concurrent.atomic} package specification for* description of the properties of atomic variables. An* {@code AtomicInteger} is used in applications such as atomically* incremented counters, and cannot be used as a replacement for an* {@link java.lang.Integer}. However, this class does extend* {@code Number} to allow uniform access by tools and utilities that* deal with numerically-based classes.** @since 1.5* @author Doug Lea
*/
public class AtomicInteger extends Number implements java.io.Serializable {private static final long serialVersionUID = 6214790243416807050L;// setup to use Unsafe.compareAndSwapInt for updatesprivate static final Unsafe unsafe = Unsafe.getUnsafe();private static final long valueOffset;static {try {valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); }}private volatile int value;/*** Creates a new AtomicInteger with the given initial value.** @param initialValue the initial value*/public AtomicInteger(int initialValue) {value = initialValue;}/*** Creates a new AtomicInteger with initial value {@code 0}.*/public AtomicInteger() {}/*** Gets the current value.** @return the current value*/public final int get() {return value;}/*** Sets to the given value.** @param newValue the new value*/public final void set(int newValue) {value = newValue;}/*** Eventually sets to the given value.** @param newValue the new value* @since 1.6*/public final void lazySet(int newValue) {unsafe.putOrderedInt(this, valueOffset, newValue);}/*** Atomically sets to the given value and returns the old value.** @param newValue the new value* @return the previous value*/public final int getAndSet(int newValue) {for (;;) {int current = get();if (compareAndSet(current, newValue))return current;}}/*** Atomically sets the value to the given updated value* if the current value {@code ==} the expected value.** @param expect the expected value* @param update the new value* @return true if successful. False return indicates that* the actual value was not equal to the expected value.*/public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}/*** Atomically sets the value to the given updated value* if the current value {@code ==} the expected value.** <p>May <a href="package-summary.html#Spurious">fail spuriously</a>* and does not provide ordering guarantees, so is only rarely an* appropriate alternative to {@code compareAndSet}.** @param expect the expected value* @param update the new value* @return true if successful.*/public final boolean weakCompareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}/*** Atomically increments by one the current value.** @return the previous value*/public final int getAndIncrement() {for (;;) {int current = get();int next = current + 1;if (compareAndSet(current, next))return current;}}/*** Atomically decrements by one the current value.** @return the previous value*/public final int getAndDecrement() {for (;;) {int current = get();int next = current - 1;if (compareAndSet(current, next))return current;}}/*** Atomically adds the given value to the current value.** @param delta the value to add* @return the previous value*/public final int getAndAdd(int delta) {for (;;) {int current = get();int next = current + delta;if (compareAndSet(current, next))return current;}}/*** Atomically increments by one the current value.** @return the updated value*/public final int incrementAndGet() {for (;;) {int current = get();int next = current + 1;if (compareAndSet(current, next))return next;}}/*** Atomically decrements by one the current value.** @return the updated value*/public final int decrementAndGet() {for (;;) {int current = get();int next = current - 1;if (compareAndSet(current, next))return next;}}/*** Atomically adds the given value to the current value.** @param delta the value to add* @return the updated value*/public final int addAndGet(int delta) {for (;;) {int current = get();int next = current + delta;if (compareAndSet(current, next))return next;}}/*** Returns the String representation of the current value.* @return the String representation of the current value.*/public String toString() {return Integer.toString(get());}public int intValue() {return get();}public long longValue() {return (long)get();}public float floatValue() {return (float)get();}public double doubleValue() {return (double)get();}}

AtomicInteger

其中的属性:

private static final long serialVersionUID = 6214790243416807050L;// setup to use Unsafe.compareAndSwapInt for updatesprivate static final Unsafe unsafe = Unsafe.getUnsafe();private static final long valueOffset;static {try {valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); }}private volatile int value;

unsafe :java中的sun.misc.Unsafe包,提供了安全访问内存的方法。这些方法提供给java访问底层的JNI(java-native-interface),因为这些方法最终是调用c/c++实现。
valueOffset:指向相对于对象起始位置的偏移量(内存中)可以理解为引用指向的内存,通过这个值可以去内存中查找某个引用在内存的值。
value:引用的当前值[预期值E]

方法示例:

public class AtomTest {private static AtomicInteger num = new  AtomicInteger(0);public static void main(String[] args) {System.out.println(num.compareAndSet(0, 1));//比较并设置,用0与内存中的值比较,相等的话,修改值为1System.out.println(num.compareAndSet(0, 1));//比较并设置,用0与内存中的值比较,相等的话,修改值为1System.out.println(num.get());;//获取初值num.set(2);//设置初值,System.out.println(num.get());System.out.println(num.decrementAndGet());//获取值自减返回减1之后的值System.out.println(num.addAndGet(2));//获取值添加2然后返回添加后的值System.out.println(num.getAndIncrement());//获取值并且自增,返回的是自增前的值System.out.println(num.getAndAdd(5));System.out.println(num.getAndDecrement());System.out.println(num.getAndSet(100));System.out.println(num.get());}
}

运行结果:

true
false
1
2
1
3
3
4
9
8
100

初始化的时候赋值为0,调用从compareAndSwap(0,1)之后返回true,compareAndSwap比较并交换,比较结果相同则修改值并返回TRUE不通则返回FALSE。

看看这个方法的源码:

/*** Atomically sets the value to the given updated value* if the current value {@code ==} the expected value.** @param expect the expected value* @param update the new value* @return true if successful. False return indicates that* the actual value was not equal to the expected value.*/public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}

compareAndSwap:当前引用值和与要更新的值以及对象,和偏移量做参数去更新内存,根据对象和偏移量可以找到引用的内存值与引用值(期望值)作比较,相等则说明这个值在此对象获得后未被修改,那么我们就可以更新,则结果为true。这个方法就是传说中的CAS,CAS相较于synchonrized提供了一种无锁的线程安全保证。

示例中的前两个操作是一样的,我们可以理解为两个线程同时更新值为1,前一个快一点,发现更新的时候内存值未变则更新,等到第二个更新的时候原始值0与内存值(此时内存值变为1了)比较则失败返回FALSE并且不进行更新。这样就保证了一致性,而我们看到变量的值value使用voliate修饰的,保证了数据的内存可见性(一个线程修改其他线程可见)。所以多个线程并发的时候这个类代替Integer能够保证安全。

这个操作非常像数据库的乐观锁,给每个表添加一个version版本号,修改数据后要持久化先查询看版本号一致不,一致的话则持久化,不一致则重新获取值进行修改。

三、AtomicLong源码以及与AtomicInteger的不同:

package java.util.concurrent.atomic;
import sun.misc.Unsafe;/*** A {@code long} value that may be updated atomically.  See the* {@link java.util.concurrent.atomic} package specification for* description of the properties of atomic variables. An* {@code AtomicLong} is used in applications such as atomically* incremented sequence numbers, and cannot be used as a replacement* for a {@link java.lang.Long}. However, this class does extend* {@code Number} to allow uniform access by tools and utilities that* deal with numerically-based classes.** @since 1.5* @author Doug Lea*/
public class AtomicLong extends Number implements java.io.Serializable {private static final long serialVersionUID = 1927816293512124184L;// setup to use Unsafe.compareAndSwapLong for updatesprivate static final Unsafe unsafe = Unsafe.getUnsafe();private static final long valueOffset;/*** Records whether the underlying JVM supports lockless* compareAndSwap for longs. While the Unsafe.compareAndSwapLong* method works in either case, some constructions should be* handled at Java level to avoid locking user-visible locks.*/static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();/*** Returns whether underlying JVM supports lockless CompareAndSet* for longs. Called only once and cached in VM_SUPPORTS_LONG_CAS.*/private static native boolean VMSupportsCS8();static {try {valueOffset = unsafe.objectFieldOffset(AtomicLong.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); }}private volatile long value;/*** Creates a new AtomicLong with the given initial value.** @param initialValue the initial value*/public AtomicLong(long initialValue) {value = initialValue;}/*** Creates a new AtomicLong with initial value {@code 0}.*/public AtomicLong() {}/*** Gets the current value.** @return the current value*/public final long get() {return value;}/*** Sets to the given value.** @param newValue the new value*/public final void set(long newValue) {value = newValue;}/*** Eventually sets to the given value.** @param newValue the new value* @since 1.6*/public final void lazySet(long newValue) {unsafe.putOrderedLong(this, valueOffset, newValue);}/*** Atomically sets to the given value and returns the old value.** @param newValue the new value* @return the previous value*/public final long getAndSet(long newValue) {while (true) {long current = get();if (compareAndSet(current, newValue))return current;}}/*** Atomically sets the value to the given updated value* if the current value {@code ==} the expected value.** @param expect the expected value* @param update the new value* @return true if successful. False return indicates that* the actual value was not equal to the expected value.*/public final boolean compareAndSet(long expect, long update) {return unsafe.compareAndSwapLong(this, valueOffset, expect, update);}/*** Atomically sets the value to the given updated value* if the current value {@code ==} the expected value.** <p>May <a href="package-summary.html#Spurious">fail spuriously</a>* and does not provide ordering guarantees, so is only rarely an* appropriate alternative to {@code compareAndSet}.** @param expect the expected value* @param update the new value* @return true if successful.*/public final boolean weakCompareAndSet(long expect, long update) {return unsafe.compareAndSwapLong(this, valueOffset, expect, update);}/*** Atomically increments by one the current value.** @return the previous value*/public final long getAndIncrement() {while (true) {long current = get();long next = current + 1;if (compareAndSet(current, next))return current;}}/*** Atomically decrements by one the current value.** @return the previous value*/public final long getAndDecrement() {while (true) {long current = get();long next = current - 1;if (compareAndSet(current, next))return current;}}/*** Atomically adds the given value to the current value.** @param delta the value to add* @return the previous value*/public final long getAndAdd(long delta) {while (true) {long current = get();long next = current + delta;if (compareAndSet(current, next))return current;}}/*** Atomically increments by one the current value.** @return the updated value*/public final long incrementAndGet() {for (;;) {long current = get();long next = current + 1;if (compareAndSet(current, next))return next;}}/*** Atomically decrements by one the current value.** @return the updated value*/public final long decrementAndGet() {for (;;) {long current = get();long next = current - 1;if (compareAndSet(current, next))return next;}}/*** Atomically adds the given value to the current value.** @param delta the value to add* @return the updated value*/public final long addAndGet(long delta) {for (;;) {long current = get();long next = current + delta;if (compareAndSet(current, next))return next;}}/*** Returns the String representation of the current value.* @return the String representation of the current value.*/public String toString() {return Long.toString(get());}public int intValue() {return (int)get();}public long longValue() {return get();}public float floatValue() {return (float)get();}public double doubleValue() {return (double)get();}}

AtomicLong源码

关于AtomicLong的用法和AtomicInteger类似:

不同之处在于多了下面一部分:一个静态的Boolean值VM_SUPPORTS_LONG_CAS,调用一个 native方法VMSupportCS8()返回虚拟机是否支持Long类型的无锁CAS机制

 /*** Records whether the underlying JVM supports lockless* compareAndSwap for longs. While the Unsafe.compareAndSwapLong* method works in either case, some constructions should be* handled at Java level to avoid locking user-visible locks.*/static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();/*** Returns whether underlying JVM supports lockless CompareAndSet* for longs. Called only once and cached in VM_SUPPORTS_LONG_CAS.*/private static native boolean VMSupportsCS8();

从这里可以看出不同的VM支持的数据类型可能有所差别,因为CAS需要硬件系统的支持。

四、AtomicReference源码及使用:

/*** An object reference that may be updated atomically. See the {@link* java.util.concurrent.atomic} package specification for description* of the properties of atomic variables.* @since 1.5* @author Doug Lea* @param <V> The type of object referred to by this reference*/
public class AtomicReference<V>  implements java.io.Serializable {private static final long serialVersionUID = -1848883965231344442L;private static final Unsafe unsafe = Unsafe.getUnsafe();private static final long valueOffset;static {try {valueOffset = unsafe.objectFieldOffset(AtomicReference.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); }}private volatile V value;/*** Creates a new AtomicReference with the given initial value.** @param initialValue the initial value*/public AtomicReference(V initialValue) {value = initialValue;}/*** Creates a new AtomicReference with null initial value.*/public AtomicReference() {}/*** Gets the current value.** @return the current value*/public final V get() {return value;}/*** Sets to the given value.** @param newValue the new value*/public final void set(V newValue) {value = newValue;}/*** Eventually sets to the given value.** @param newValue the new value* @since 1.6*/public final void lazySet(V newValue) {unsafe.putOrderedObject(this, valueOffset, newValue);}/*** Atomically sets the value to the given updated value* if the current value {@code ==} the expected value.* @param expect the expected value* @param update the new value* @return true if successful. False return indicates that* the actual value was not equal to the expected value.*/public final boolean compareAndSet(V expect, V update) {return unsafe.compareAndSwapObject(this, valueOffset, expect, update);}/*** Atomically sets the value to the given updated value* if the current value {@code ==} the expected value.** <p>May <a href="package-summary.html#Spurious">fail spuriously</a>* and does not provide ordering guarantees, so is only rarely an* appropriate alternative to {@code compareAndSet}.** @param expect the expected value* @param update the new value* @return true if successful.*/public final boolean weakCompareAndSet(V expect, V update) {return unsafe.compareAndSwapObject(this, valueOffset, expect, update);}/*** Atomically sets to the given value and returns the old value.** @param newValue the new value* @return the previous value*/public final V getAndSet(V newValue) {while (true) {V x = get();if (compareAndSet(x, newValue))return x;}}/*** Returns the String representation of the current value.* @return the String representation of the current value.*/public String toString() {return String.valueOf(get());}}

AtomicReference源码

除了类型是个对象的之外,方法使用和AtomicInteger一模一样:

AtomicReference的使用:

import java.util.concurrent.atomic.AtomicReference;public class AtomTest {private static AtomicReference<Pig> pigtest;public static void main(String[] args) {Pig pig = new Pig("猪坚强", 2);Pig pig2 = new Pig("猪八戒", 2);System.out.println("pig_hashCode:"+pig.hashCode());System.out.println("pig2_hashCode:"+pig2.hashCode());AtomTest.pigtest = new AtomicReference<Pig>(pig);System.out.println(pigtest.get().toString());System.out.println(pigtest.get().hashCode());System.out.println(pigtest.compareAndSet(pig, pig2));System.out.println(pigtest.compareAndSet(pig, pig2));System.out.println(pigtest.get().toString());System.out.println(pigtest.get().hashCode());}
}

运行结果:

pig_hashCode:779824645
pig2_hashCode:420110874
[猪坚强,2]
779824645
true
false
[猪八戒,2]
420110874

第一步:先获取pigTest的toString(),和hashCode,结果是【猪坚强,2】,hashCode:779824645【我们发现hashCode居然==pig_hashCode】

第二步:进行CAS修改为pig2结果为true,再进行一次结果为FALSE,

第三步:获取pigTest的toString(),和hashCode,结果是【猪八戒,2】,hashCode:420110874【我们发现hashCode居然==pig2_hashCode】

结果说明了:pigtest是一个指向对象引用的引用,利用CAS操作可以修改pigTest指向的对象引用从而改变自身指向的对象。第一次CAS成功将pigtest指向的pig修改成了pig2.第二次CAS操作失败了,可以保证多线程并发时的安全问题。

五、总结:

  Atomic包下内容并不复杂,一句话来说就是提供了CAS无锁的安全访问机制。表现出来的是通过期望值E与内存值M作比较,相同则修改内存值M为更新值U。四个参数:当前对象this,偏移量V,期望值E,更新值U。

  利用CAS+voliate+native的机制保证数据操作的原子性,可见性和一致性。voliate使变量可见,CAS调用unsafe中的native方法访问系统底层的实现。unsafe中的这些方法直接操作内存,运用不当可能造成很大的问题。

  其实从Atomic包中的原子类的探索中,只是想引出CAS这个概念,CAS同样提供了一种线程安全的机制,而它不同于Synchonrized,synchonrized被称之为重量级锁,原因是因为粒度太强,加锁就代表着线程阻塞,高并发访问时带来的性能问题是硬伤。

  为了解决这种问题出现了两种机制:一种就是CAS,另一种是锁优化。

  CAS是将阻塞下降到了底层CPU上(纯属个人理解,因为看到有权威是说存在阻塞的,可能让别的线程知道已经更改了数据并且更新失败也是一种阻塞吧),语言层面访问效率远远低于系统内部硬件上,尽管同样是阻塞,在系统内部加锁解锁的效率要高很多。但是需要的是硬件支持,不过现在绝大部分CPU都已经支持CAS了。

  unsafe类:http://www.cnblogs.com/mickole/articles/3757278.html大家可以看这篇博客。

转载于:https://www.cnblogs.com/NextNight/p/6600343.html

[Java多线程]-J.U.C.atomic包下的AtomicInteger,AtomicLong等类的源码解析相关推荐

  1. Java生鲜电商平台-电商会员体系系统的架构设计与源码解析

    Java生鲜电商平台-电商会员体系系统的架构设计与源码解析 说明:Java生鲜电商平台中会员体系作为电商平台的基础设施,重要性不容忽视.我去年整理过生鲜电商中的会员系统,但是比较粗,现在做一个最好的整 ...

  2. 【多线程】ThreadPoolExecutor类万字源码解析(注解超级详细)

    线程池 线程池初始化时是没有创建线程的,线程池里的线程的初始化与其他线程一样,但是在完成任务以后,该线程不会自行销毁,而是以挂起的状态返回到线程池.直到应用程序再次向线程池发出请求时,线程池里挂起的线 ...

  3. Linux-4.20.8内核桥收包源码解析(一)----------sk_buff(详细)

    作者:lwyang? 内核版本:Linux-4.20.8 网络子系统中用来存储数据的缓冲区叫做套接字缓存,简称SKB,可处理变长数据,尽量避免数据的复制. 每一个SKB都在设备中标识发送报文的目的或接 ...

  4. java扫描指定package注解_java获取包下被指定注解的类

    方案一: 采用reflections 框架(此框架依赖com.google.guava) 2.项目依赖 org.reflections reflections 0.9.11 com.google.gu ...

  5. 面试官系统精讲Java源码及大厂真题 - 33 CountDownLatch、Atomic 等其它源码解析

    33 CountDownLatch.Atomic 等其它源码解析 每个人的生命都是一只小船,理想是小船的风帆. 引导语 本小节和大家一起来看看 CountDownLatch 和 Atomic 打头的原 ...

  6. 基于Java毕业设计疫情下的进出口食品安全信息管理系统源码+系统+mysql+lw文档+部署软件

    基于Java毕业设计疫情下的进出口食品安全信息管理系统源码+系统+mysql+lw文档+部署软件 基于Java毕业设计疫情下的进出口食品安全信息管理系统源码+系统+mysql+lw文档+部署软件 本源 ...

  7. 计算机毕业设计Java疫情状态下的图书馆座位预约系统(系统+源码+mysql数据库+Lw文档)

    计算机毕业设计Java疫情状态下的图书馆座位预约系统(系统+源码+mysql数据库+Lw文档) 计算机毕业设计Java疫情状态下的图书馆座位预约系统(系统+源码+mysql数据库+Lw文档) 本源码技 ...

  8. java计算机毕业设计微服务”架构下新闻头条的设计与实现源码+系统+数据库+lw文档

    java计算机毕业设计微服务"架构下新闻头条的设计与实现源码+系统+数据库+lw文档 java计算机毕业设计微服务"架构下新闻头条的设计与实现源码+系统+数据库+lw文档 本源码技 ...

  9. 基于JAVA疫情下居家隔离服务系统计算机毕业设计源码+系统+数据库+lw文档+部署

    基于JAVA疫情下居家隔离服务系统计算机毕业设计源码+系统+数据库+lw文档+部署 基于JAVA疫情下居家隔离服务系统计算机毕业设计源码+系统+数据库+lw文档+部署 本源码技术栈: 项目架构:B/S ...

最新文章

  1. 使用Hystrix守护应用(3)
  2. 微视已死,腾讯战略放弃微视,大牛纷纷离职,PMcaff--行业内部解读
  3. webpack打包后引用cdn的js_呕心沥血编写的webpack多入口零基础配置 【建议收藏】...
  4. thinkphp整合Ueditor编辑器
  5. OCS2007R2部署之四部署存档和监控服务器
  6. UITableView单元格选择颜色?
  7. Vue:带参数函数在传递参数的同时传递事件对象
  8. css 水印_自制腾讯视频去除水印Chrome插件!厉害吧!
  9. 大数据开发比赛echarts所有要学习的主要图表 简单化 得分点
  10. 怎么选择企业即时通讯软件
  11. python制作的简单程序_Python如何制作简易收银小程序
  12. npm 包解析 eml 文件
  13. filebeat报错error pipeline/output.go:100 failed to connect to backoff(async(tcp://xx.xx.xx.xx:))...
  14. 修改Google Chrome主页
  15. 冒泡排序【Java】
  16. 【小程序动画合集】10种小程序动画效果实现方法,文章太长建议收藏!
  17. 使用ClickHouse JDBC官方驱动,踩坑无数
  18. 多多情报通:拼多多店铺不交保证金能卖货吗?有什么影响吗?
  19. puppy linux php,Puppy Linux(小巧实用操作系统)
  20. 前端作品之静态页面设计(二):网易云音乐页界面静态效果设计(iphonex兼容)

热门文章

  1. 计算机心得300,计算机实训总结计算机实训心得300
  2. linux date 小写h,linux date 命令详解[转载]
  3. randn函数加噪声_NLP入门指南01:感知机、激活函数、损失函数
  4. windows tasklist 查看应用、进程
  5. 【spring boot】ajax post提交遇到403
  6. python 判断子序列_LeetCode 392. 判断子序列 | Python
  7. cpp判断输入为数字_猜数字小程序带你C语言入门
  8. 命令行请求jsp页面_JSP 之 8种HTTP的请求方式 之 页面组成等
  9. decode函数吗 jsp_JSP中js传递和解析URL参数以及中文转码和解码问题
  10. android加载圈,SwipeRefreshLayout加载圈不会隐藏在android中