[Java多线程]-J.U.C.atomic包下的AtomicInteger,AtomicLong等类的源码解析
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等类的源码解析相关推荐
- Java生鲜电商平台-电商会员体系系统的架构设计与源码解析
Java生鲜电商平台-电商会员体系系统的架构设计与源码解析 说明:Java生鲜电商平台中会员体系作为电商平台的基础设施,重要性不容忽视.我去年整理过生鲜电商中的会员系统,但是比较粗,现在做一个最好的整 ...
- 【多线程】ThreadPoolExecutor类万字源码解析(注解超级详细)
线程池 线程池初始化时是没有创建线程的,线程池里的线程的初始化与其他线程一样,但是在完成任务以后,该线程不会自行销毁,而是以挂起的状态返回到线程池.直到应用程序再次向线程池发出请求时,线程池里挂起的线 ...
- Linux-4.20.8内核桥收包源码解析(一)----------sk_buff(详细)
作者:lwyang? 内核版本:Linux-4.20.8 网络子系统中用来存储数据的缓冲区叫做套接字缓存,简称SKB,可处理变长数据,尽量避免数据的复制. 每一个SKB都在设备中标识发送报文的目的或接 ...
- java扫描指定package注解_java获取包下被指定注解的类
方案一: 采用reflections 框架(此框架依赖com.google.guava) 2.项目依赖 org.reflections reflections 0.9.11 com.google.gu ...
- 面试官系统精讲Java源码及大厂真题 - 33 CountDownLatch、Atomic 等其它源码解析
33 CountDownLatch.Atomic 等其它源码解析 每个人的生命都是一只小船,理想是小船的风帆. 引导语 本小节和大家一起来看看 CountDownLatch 和 Atomic 打头的原 ...
- 基于Java毕业设计疫情下的进出口食品安全信息管理系统源码+系统+mysql+lw文档+部署软件
基于Java毕业设计疫情下的进出口食品安全信息管理系统源码+系统+mysql+lw文档+部署软件 基于Java毕业设计疫情下的进出口食品安全信息管理系统源码+系统+mysql+lw文档+部署软件 本源 ...
- 计算机毕业设计Java疫情状态下的图书馆座位预约系统(系统+源码+mysql数据库+Lw文档)
计算机毕业设计Java疫情状态下的图书馆座位预约系统(系统+源码+mysql数据库+Lw文档) 计算机毕业设计Java疫情状态下的图书馆座位预约系统(系统+源码+mysql数据库+Lw文档) 本源码技 ...
- java计算机毕业设计微服务”架构下新闻头条的设计与实现源码+系统+数据库+lw文档
java计算机毕业设计微服务"架构下新闻头条的设计与实现源码+系统+数据库+lw文档 java计算机毕业设计微服务"架构下新闻头条的设计与实现源码+系统+数据库+lw文档 本源码技 ...
- 基于JAVA疫情下居家隔离服务系统计算机毕业设计源码+系统+数据库+lw文档+部署
基于JAVA疫情下居家隔离服务系统计算机毕业设计源码+系统+数据库+lw文档+部署 基于JAVA疫情下居家隔离服务系统计算机毕业设计源码+系统+数据库+lw文档+部署 本源码技术栈: 项目架构:B/S ...
最新文章
- 使用Hystrix守护应用(3)
- 微视已死,腾讯战略放弃微视,大牛纷纷离职,PMcaff--行业内部解读
- webpack打包后引用cdn的js_呕心沥血编写的webpack多入口零基础配置 【建议收藏】...
- thinkphp整合Ueditor编辑器
- OCS2007R2部署之四部署存档和监控服务器
- UITableView单元格选择颜色?
- Vue:带参数函数在传递参数的同时传递事件对象
- css 水印_自制腾讯视频去除水印Chrome插件!厉害吧!
- 大数据开发比赛echarts所有要学习的主要图表 简单化 得分点
- 怎么选择企业即时通讯软件
- python制作的简单程序_Python如何制作简易收银小程序
- npm 包解析 eml 文件
- filebeat报错error pipeline/output.go:100 failed to connect to backoff(async(tcp://xx.xx.xx.xx:))...
- 修改Google Chrome主页
- 冒泡排序【Java】
- 【小程序动画合集】10种小程序动画效果实现方法,文章太长建议收藏!
- 使用ClickHouse JDBC官方驱动,踩坑无数
- 多多情报通:拼多多店铺不交保证金能卖货吗?有什么影响吗?
- puppy linux php,Puppy Linux(小巧实用操作系统)
- 前端作品之静态页面设计(二):网易云音乐页界面静态效果设计(iphonex兼容)
热门文章
- 计算机心得300,计算机实训总结计算机实训心得300
- linux date 小写h,linux date 命令详解[转载]
- randn函数加噪声_NLP入门指南01:感知机、激活函数、损失函数
- windows tasklist 查看应用、进程
- 【spring boot】ajax post提交遇到403
- python 判断子序列_LeetCode 392. 判断子序列 | Python
- cpp判断输入为数字_猜数字小程序带你C语言入门
- 命令行请求jsp页面_JSP 之 8种HTTP的请求方式 之 页面组成等
- decode函数吗 jsp_JSP中js传递和解析URL参数以及中文转码和解码问题
- android加载圈,SwipeRefreshLayout加载圈不会隐藏在android中