原子操作:不能被分割(中断)的一个或一系列操作叫原子操作。

原子操作Atomic主要有12个类,4种类型的原子更新方式,原子更新基本类型,原子更新数组,原子更新字段,原子更新引用。Atomic包中的类基本都是使用Unsafe实现的包装类。

基本类型:AtomicInteger,AtomicLong,AtomicBoolean;

引用类型:AtomicReference、AtomicReference的ABA实例、AtomicStampedRerence、AtomicMarkableReference;

数组类型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray;

属性原子修改器(Updater):AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater;

1、原子更新基本类型类

  用于通过原子的方式更新基本类型,Atomic包提供了以下三个类: AtomicBoolean:原子更新布尔类型。 AtomicInteger:原子更新整型。 AtomicLong:原子更新长整型。 AtomicInteger的常用方法如下: int addAndGet(int delta) :以原子方式将输入的数值与实例中的值 (AtomicInteger里的value)相加,并返回结果 boolean compareAndSet(int expect, int update) :如果输入的数值等于值,则以原子方式将该值设置为输入的值。 int getAndIncrement():以原子方式将当前值加1,注意:这里返回的是自前的值。void lazySet(int newValue):最终会设置成newValue,使用lazySet设置后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。 int getAndSet(int newValue):以原子方式设置为newValue的值,并返回值。 Atomic包提供了三种基本类型的原子更新,但是Java的基本类型里还有char,fldouble等。那么问题来了,如何原子的更新其他的基本类型呢?Atomic包里的类基本使用Unsafe实现的,Unsafe只提供了三种CAS方法,compareAndSwapObject, compareAndSwapInt和compareAndSwapLong,再看AtomicBoolean源码,发现先把Boolean转换成整型,再使用compareAndSwapInt进行CAS,所以原子更新dou也可以用类似的思路来实现。

下面我们来看一下每种类型的一个实例:

/**
* <p>Title: AtomicIntegerTest.java</p >
* <p>Description: </p >
* <p>Copyright: NTT DATA Synergy All Rights Reserved.</p >
* <p>Company: www.synesoft.com.cn</p >
* <p>@datetime 2019年8月9日 上午8:01:30</p >
* <p>$Revision$</p >
* <p>$Date$</p >
* <p>$Id$</p >
*/
package com.test; import java.util.concurrent.atomic.AtomicInteger; /**
* @author hong_liping
*
*/
public class AtomicIntegerTest {
static AtomicInteger ai=new AtomicInteger();
public static void main(String[] args) {
for(int i=0;i<10;i++){
new Thread(new Runnable() { @Override
public void run() {
ai.incrementAndGet(); }
}).start();
}
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println("循环后的结果如下:"+ai.get());
} }
//测试结果
循环后的结果如下:9
循环后的结果如下:10

根据上面的代码,我们多运行几次,会发现,代码的测试结果一会儿是9一会儿是10,不是10,为什么呢,因为线程还没有跑完,我下面的就已经打出来了,让线程睡眠一下就可以解决这个问题了。

下面我们来看一下atomic的ABA问题,这个问题在面试的时候经常问到。

/**
* <p>Title: AtomicTest.java</p >
* <p>Description: </p >
* <p>@datetime 2019年8月8日 下午3:40:37</p >
* <p>$Revision$</p >
* <p>$Date$</p >
* <p>$Id$</p >
*/
package com.test; import java.util.concurrent.atomic.AtomicInteger; /**
* @author hong_liping
*
*/
public class AtomicAbaTest {
private static AtomicInteger ato=new AtomicInteger(1);
public static void main(String[] args) {
Thread mainT=new Thread(new Runnable() { @Override
public void run() {
int a=ato.get();
System.out.println(Thread.currentThread().getName()+"原子操作修改前数据"+a);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean successFlag=ato.compareAndSet(a, 2);
if(successFlag){
System.out.println(Thread.currentThread().getName()+"原子操作修改后数据"+ato.get());
} }
},"mainT"); Thread otherT=new Thread(new Runnable() { @Override
public void run() {
int b=ato.incrementAndGet();//1+1
System.out.println(Thread.currentThread().getName()+"原子操作自增后数据"+b);
b=ato.decrementAndGet();//2-1
System.out.println(Thread.currentThread().getName()+"原子操作自减后数据"+b);
}
},"OtherT"); mainT.start();
otherT.start();
} } 测试结果:

OtherT原子操作自增后数据2
mainT原子操作修改前数据1
OtherT原子操作自减后数据1
mainT原子操作修改后数据2

 

根据上面的操作,我们可以看到的是AtomicInteger的操作自增,自减,值的替换等。但是此处应当注意的是原子操作存在一个ABA问题,ABA问题的现象就是:mainT执行完成后的值2(替换的2),otherT在执行2-1的时候的2是自增(1+1)的结果。在这两个线程中用到的2不是同一个2,就相当于是一个漏洞,相当于说你从王健林账号中偷走了10个亿去投资,等你投资好了回本了,你再把这10个亿打回了王健林账号,这整个过程王建林没有发现,你的整个操作过程也没有记录,所以对于王健林来说他的钱没有丢失过,还是放在那里的。很明显要解决这个ABA问题最好的办法就是每一步操作都打个标记,相当于一个银行的流水,这样你偷钱,还钱的整个过程就有一个出,一个入,王健林看的时候就会发现我的总金没有变,但是操作记录显示我的钱曾经被人盗了然后又被人还回来了。这就需要用到AtomicStampeReference.

2、原子更新引用类型

原子更新基本类型的AtomicInteger,只能更新一个变量,如果要原子的更新多个变 量,就需要使用这个原子更新引用类型提供的类。Atomic包提供了以下三个类: AtomicReference:原子更新引用类型。 AtomicReferenceFieldUpdater:原子更新引用类型里的字段。 AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子的更 新一个布尔类型的标记位和引用类型。构造方法是AtomicMarkableReference(V initialRef, boolean initialMark)

接下来我们来看一下AtomicStampedReference的测试类:

/**
* <p>Title: AtomicStampedReference.java</p >
* <p>Description: </p >
* <p>@datetime 2019年8月9日 上午8:35:56</p >
* <p>$Revision$</p >
* <p>$Date$</p >
* <p>$Id$</p >
*/
package com.test; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference; /**
* @author hong_liping
*
*/
public class AtomicStampedReferenceTest { private static AtomicStampedReference<Integer> asf=new AtomicStampedReference<Integer>(1, 0);
public static void main(String[] args) {
Thread mainT=new Thread(new Runnable() { @Override
public void run() {
int stamp= asf.getStamp();
System.out.println(Thread.currentThread().getName()+"原子操作修改前数据"+asf.getReference()+
"_"+stamp);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//此时expectedReference未发生改变,但是stamp已经被修改了,所以CAS失败
boolean successFlag=asf.compareAndSet(1, 2, stamp, stamp+1);
if(successFlag){
System.out.println(Thread.currentThread().getName()+"原子操作修改后数据"+asf.getReference()+
"_"+stamp);
}else{
System.out.println(Thread.currentThread().getName()+"cas操作失败");
} }
},"mainT"); Thread otherT=new Thread(new Runnable() { @Override
public void run() {
int stamp=asf.getStamp();
asf.compareAndSet(1, 2, stamp, stamp+1);
System.out.println(Thread.currentThread().getName()+"原子操作自增后数据"+asf.getReference()+
"_"+asf.getReference()); asf.compareAndSet(2, 1, stamp, stamp+1);
System.out.println(Thread.currentThread().getName()+"原子操作自减后数据"+asf.getReference()+
"_"+stamp);;
}
},"OtherT"); mainT.start();
otherT.start();
} }
//测试结果:
mainT原子操作修改前数据2_0
OtherT原子操作自增后数据2_2
OtherT原子操作自减后数据2_0
mainTcas操作失败
3、原子更新数组类
  通过原子的方式更新数组里的某个元素,Atomic包提供了以下三个类AtomicIntegerArray:原子更新整型数组里的元素。AtomicLongArray:原子更新长整型数组里的元素。 AtomicReferenceArray:原子更新引用类型数组里的元素。 omicIntegerArray类主要是提供原子的方式更新数组里的整型,其常用方法int addAndGet(int i, int delta):以原子方式将输入值与数组中索加。boolean compareAndSet(int i, int expect, int update):如果值,则以原子方式将数组位置i的元素设置成update值。

接下来我们来看一下AtomicIntegerArray的一个案例

/**
* <p>Title: AtomicArrayTest.java</p >
* <p>Description: </p >
* <p>@datetime 2019年8月10日 上午9:45:49</p >
* <p>$Revision$</p >
* <p>$Date$</p >
* <p>$Id$</p >
*/
package com.test; import java.util.concurrent.atomic.AtomicIntegerArray; import com.sun.org.apache.bcel.internal.generic.NEWARRAY; /**
* @author hong_liping
*
*/
public class AtomicArrayTest {
static int[] array=new int[]{1,2,3};
static AtomicIntegerArray aia=new AtomicIntegerArray(array);
public static void main(String[] args) {
aia.getAndSet(1, 5);
System.out.println(aia.get(1));
System.out.println(array[1]);
if(aia.get(1)==array[1]){
System.out.println("数组中的值与原子数组中的相等");
}else{
System.out.println("数组中的值与原子数组中的不相等");
}
} }
结果:

5
2
数组中的值与原子数组中的不相等

 

由以上的代码可以看出原子数组与我本身定义的数据同一个下标下的值是不一样的,为什么呢,我们看一下源码就会发现原子数据操作的并不是我定义的变量本身,而是先拷贝一份,然后操作的是拷贝的版本。

 public AtomicIntegerArray(int[] array) {
// Visibility guaranteed by final field guarantees
this.array = array.clone();//初始化数组的时候拷贝
}
public final int getAndSet(int i, int newValue) {
return unsafe.getAndSetInt(array, checkedByteOffset(i), newValue);
}

在进行数据原子操作的时候使用的是魔术类Unsafe.

4、原子更新字段类

如果我们只需要某个类里的某个字段,那么就需要使用原子更新字段类,Atomic包提
供了以下三个类:
AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
AtomicLongFieldUpdater:原子更新长整型字段的更新器。
AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值
与引用关联起来,可用于原子的更数据和数据的版本号,可以解决使用CAS进行原子
更新时,可能出现的ABA问题。原子更新字段类都是抽象类,每次使用都时候必须使用静态方法newUpdater创建一个
更新器。原子更新类的字段的必须使用public volatile修饰符。

接下来我们再来看看AtomicIngerFieldUpdater

/**
* <p>Title: AtomicIntegerFieldUpdateTest.java</p >
* <p>Description: </p >
* <p>@datetime 2019年8月10日 上午10:02:22</p >
* <p>$Revision$</p >
* <p>$Date$</p >
* <p>$Id$</p >
*/
package com.test; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; /**
* @author hong_liping
*
*/
public class AtomicIntegerFieldUpdateTest {
static AtomicIntegerFieldUpdater aifu=AtomicIntegerFieldUpdater.newUpdater(Person.class, "age"); static class Person{
private String name;
public volatile int age;
public Person(String name,int age){
this.name=name;
this.age=age;
}
public int getAge(){
return age;
}
} public static void main(String[] args) {
Person person=new Person("张三", 18);
System.out.println(aifu.getAndIncrement(person));
System.out.println(aifu.get(person));
} } 测试结果:

18
19

在age属性上加volatile是为了保证在多线程并发的情况下保证可见性。

Unsafe

Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。 Unsafe类为一单例实现,提供静态方法getUnsafe获取Unsafe实例,当且仅当调用getUnsafe方法的类为引导类加载器所加载时才合法,否则抛出SecurityException异常。

@CallerSensitive
/* */ public static Unsafe getUnsafe()
/* */ {
/* 88 */ Class localClass = Reflection.getCallerClass();
/* 89 */ if (!VM.isSystemDomainLoader(localClass.getClassLoader()))// 仅在引导类加载器`BootstrapClassLoader加载时才合法
/* 90 */ throw new SecurityException("Unsafe");
/* 91 */ return theUnsafe;
/* */ }
/* */

Unsafe经常用到的就是CAS,内存屏障(禁止load,store重新排序),线程调度(线程挂起,恢复还有获取,释放锁)。

如何获取Unsafe,1、把调用Unsafe相关方法的类Demo所在jar包路径追加到默认的bootstrap路径中,使得A被引导类加载器加载 java -Xbootclasspath/Demo:${path} // 其中path为调用Unsafe相关方法的类所在jar包路径

2、通过反射获取单例对象theUnsafe

我们可以看一下下面的一个代码:

public class UnsafeInstance {
public static Unsafe reflectGetUnsafe(){
Field field;
try {
field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

接下来再来看一个利用Unsafe的代码:

/**
* <p>Title: AtomicUnsafeUpdaterTest.java</p >
* <p>Description: </p >
* <p>@datetime 2019年8月10日 上午10:57:23</p >
* <p>$Revision$</p >
* <p>$Date$</p >
* <p>$Id$</p >
*/
package com.test; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import sun.misc.Unsafe; /**
* @author hong_liping
*
*/
public class AtomicUnsafeUpdaterTest {
private String name;
private volatile int age;
private static final Unsafe unsafe=UnsafeInstance.reflectGetUnsafe();
private static final long valueOffset;
static{
try {
valueOffset=unsafe.objectFieldOffset(AtomicUnsafeUpdaterTest.class.getDeclaredField("age"));//偏移量
System.out.println("initial valueOffset is "+valueOffset);
} catch (Exception e) {
throw new Error(e);
}
}
public void compareAndSwapAge(int old,int target){
unsafe.compareAndSwapInt(this, valueOffset, old, target);
} public AtomicUnsafeUpdaterTest(String name,int age){
this.name=name;
this.age=age;
}
public int getAge(){
return this.age;
}
public static void main(String[] args) {
AtomicUnsafeUpdaterTest test=new AtomicUnsafeUpdaterTest("美女",30);
test.compareAndSwapAge(30, 25);
System.out.println("年龄变换后的值为"+test.getAge());
}
}

1、CAS(unsafe的用法)的几个重要方法以及参数:

/** * CAS
* @param o 包含要修改field的对象
* @param offset 对象中某field的偏移量
* @param expected 期望值
* @param update 更新值
* @return true | false */
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

上述中的偏移量是什么呢,我们来看一下:AtomicUnsafeUpdaterTest的实现中,静态字段valueOffset即为字段value的内存偏移地址,valueOffset的值在AtomicInteger初始化时,在静态代码块中通过Unsafe的objectFieldOffset方法获取。在AtomicInteger中提供的线程安全方法中,通过字段valueOffset的值可以定位到AtomicUnsafeUpdaterTest对象中value的内存地址,从而可以根据CAS实现对value字段的原子操作。

下图为某个AtomicInteger对象自增操作前后的内存示意图,对象的基地址baseAddress=“0x110000”,通过baseAddress+valueOffset得到value的内存地valueAddress=“0x11000c”;然后通过CAS进行原子性的更新操作,成功则返回,否则继续重试,直到更新成功为止。

2、unsafe线程调度

包括线程挂起、恢复、锁机制等方法。

//取消阻塞线程
public native void unpark(Object thread);
//阻塞线程
public native void park(boolean isAbsolute, long time);
//获得对象锁(可重入锁)
@Deprecated
public native void monitorEnter(Object o);
//释放对象锁
@Deprecated
public native void monitorExit(Object o);
//尝试获取对象锁
@Deprecated
public native boolean tryMonitorEnter(Object o);
方法park、unpark即可实现线程的挂起与恢复,将一个线程进行挂起是通过park方法实现的,调用park方法后,线程将一直阻塞直到超时或者中断等条件出现;unpark可以终止一个挂起的线程,使其恢复正常。在使用park和unpark的时候是可以颠倒的,先使用unpark,相当于取得一张票,在使用park的时候相当于使用这张票。
典型应用
Java锁和同步器框架的核心类AbstractQueuedSynchronizer,就是通过调用LockSupport.park()和LockSupport.unpark()实现线程的阻塞和唤醒的,而LockSupport的park、unpark方法实际是调用Unsafe的park、unpark方式来实现。 
public class ThreadParkerTest {

    public static void main(String[] args) {

        /*Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread - is running----");
LockSupport.park();//阻塞当前线程
System.out.println("thread is over-----");
}
}); t.start(); try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.unpark(t);//唤醒指定的线程*/ //拿出票据使用
LockSupport.park(); System.out.println("main thread is over");
//相当于先往池子里放了一张票据
LockSupport.unpark(Thread.currentThread());//Pthread_mutex System.out.println("im running step 1"); } }
public class ObjectMonitorTest {
static Object object = new Object(); /* public void method1(){
unsafe.monitorEnter(object);
} public void method2(){
unsafe.monitorExit(object);
}*/ public static void main(String[] args) { /*synchronized (object){
}*/
Unsafe unsafe = UnsafeInstance.reflectGetUnsafe(); unsafe.monitorEnter(object);//获取锁
//业务逻辑写在此处之间
unsafe.monitorExit(object);//锁释放 }

 

3、内存屏障

在Java 8中引入,用于定义内存屏障(也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的
所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序。
//内存屏障,禁止load操作重排序。屏障前的load操作不能被重排序到屏障后,屏障后的load操作不能被重排序到屏障前
public native void loadFence();
//内存屏障,禁止store操作重排序。屏障前的store操作不能被重排序到屏障后,屏障后的store操作不能被重排序到屏障前
public native void storeFence();
//内存屏障,禁止load、store操作重排序
public native void fullFence();
典型应用
在Java 8中引入了一种锁的新机制——StampedLock,它可以看成是读写锁的一个改进版本。StampedLock提供了一种乐观读锁的实现,这种乐观读锁类似于无锁的操作,完
全不会阻塞写线程获取写锁,从而缓解读多写少时写线程“饥饿”现象。由于StampedLock提供的乐观读锁不阻塞写线程获取读锁,当线程共享变量从主内存load到线程工作内存时,会存在数据不一致问题,所以当使用StampedLock的乐观读锁时,需要遵从如下图用例中使用的模式来确保数据的一致性。

public class FenceTest {

    public static void main(String[] args) {

        UnsafeInstance.reflectGetUnsafe().loadFence();//读屏障

        UnsafeInstance.reflectGetUnsafe().storeFence();//写屏障

        UnsafeInstance.reflectGetUnsafe().fullFence();//读写屏障

    }
}

以上就是关于原子操作和Unsafe的解读,欢迎留言评论,谢谢。

并发编程之原子操作Atomic&Unsafe的更多相关文章

  1. C++11并发编程:原子操作atomic

    一:概述 项目中经常用遇到多线程操作共享数据问题,常用的处理方式是对共享数据进行加锁,如果多线程操作共享变量也同样采用这种方式. 为什么要对共享变量加锁或使用原子操作?如两个线程操作同一变量过程中,一 ...

  2. 并发编程之原子Atomic&Unsafe

    1.原子更新基本类型类   用于通过原子的方式更新基本类型,Atomic包提供了以下三个类: AtomicBoolean:原子更新布尔类型. AtomicInteger:原子更新整型. AtomicL ...

  3. c++并发编程之原子操作的实现原理

    原子(atomic)本意是”不能被进一步分割的最小粒子”,而原子操作(atomic operation)意为”不可被中断的一个或一系列操作”. 处理器如何实现原子操作 (1) 使用总线锁保证原子性 如 ...

  4. 并发编程-JUC之Atomic

    概述: 早期的JDK版本中,如果要并发的对Integer.Long.Double之类的Java原始类型或引用类型进行操作,一般都需要通过锁来控制并发,以防止数据不一致.JUC-Atomic原子类位于j ...

  5. java并发编程之原子操作

    先来看一段简单的代码,稍微有点并发知识的都可以知道打印出结果必然是一个小于20000的值 package com.example.test.cas; import java.io.IOExceptio ...

  6. [转]JAVA并发编程学习笔记之Unsafe类

    1.通过Unsafe类可以分配内存,可以释放内存:类中提供的3个本地方法allocateMemory.reallocateMemory.freeMemory分别用于分配内存,扩充内存和释放内存,与C语 ...

  7. Java并发编程之原子操作类

    什么是原子操作类当更新一个变量的时候,多出现数据争用的时候可能出现所意想不到的情况.这时的一般策略是使用synchronized解决,因为synchronized能够保证多个线程不会同时更新该变量.然 ...

  8. 从CAS讲起,真正高性能解决并发编程的原子操作

    今天是猿灯塔“365天原创计划”第1天. 一.原子性操作 原子性操作:原子性在一个操作是不可中断的,要么全部执行成功要么全部执行失败,有着“同生共死”的感觉.及时在多个线程一起执行的时候,一个操作一旦 ...

  9. 并发编程(一)—— volatile关键字和 atomic包

    本文将讲解volatile关键字和 atomic包,为什么放到一起讲呢,主要是因为这两个可以解决并发编程中的原子性.可见性.有序性,让我们一起来看看吧. Java内存模型 JMM(java内存模型) ...

随机推荐

  1. SpringAop应用

    1. 引言 为什么要使用Aop?贴一下较为官方的术语: 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方 式和运行期动态代理实现程序功能 ...

  2. 关于JQuery.form.js异步上传文件点滴

    好久没动代码了,前几天朋友委托我帮忙给做几个页面,其中有个注册带图片上传的页面.已之前的经验应该很快就能搞定,没想到的是搞了前后近一天时间.下面就说说异步上传的重要几个点,希望自己下次遇到此类问题的时 ...

  3. java中存储机制堆栈。

    一.java的六种存储地址及解释 1) 寄存器(register):这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部.但是寄存器的数量极其有限,所以寄存器由编译器根据需求进行分配.你不 ...

  4. Spring Cloud Alibaba | Sentinel:分布式系统的流量防卫兵进阶实战

    Spring Cloud Alibaba | Sentinel:分布式系统的流量防卫兵进阶实战 在阅读本文前,建议先阅读<Spring Cloud Alibaba | Sentinel:分布式系 ...

  5. 聊聊 Python 的单元测试框架(二):nose 和它的继任者 nose2

    作者:HelloGitHub-Prodesire HelloGitHub 的<讲解开源项目>系列,项目地址:https://github.com/HelloGitHub-Team/Arti ...

  6. J2EE简单的分页器

    J2EE项目特别是后台管理,或者一部分前台展示数据比较多,通常需要分页把展示折叠在数据区内. 一般有几种方式来实现分页, 一是官方分页插件,二是自己写,三是网上找(类似于第一种) 这里就介绍第二种, ...

  7. ZK Watcher 的原理和实现

    什么是 ZK Watcher 基于 ZK 的应用程序的一个常见需求是需要知道 ZK 集合的状态.为了达到这个目的,一种方法是 ZK 客户端定时轮询 ZK 集合,检查系统状态是否发生了变化.然而,轮询并 ...

  8. 新建web工程

    1.选择新建Dynamic  Web Project 2.选择服务器和版本(2.5) 3.WebContend目录下新建一个html文件 4.运行 工程的目录结构: WEB-INF目录时受保护的,不能 ...

  9. spring中集成shiro进行安全管理

    shiro是一款轻量级的安全框架,提供认证.授权.加密和会话管理四个基础功能,除此之外也提供了很好的系统集成方案. 下面将它集成到之前的demo中,在之前spring中使用aop配置事务这篇所附代码的 ...

  10. Matplotlib散点图、条形图、直方图-02

    对比常用统计图 折线图: 特点:能够显示数据的变化趋势,反映事物的变化情况.(变化) 直方图: 特点:绘制连续性的数据,展示一组或者多组数据的分布情况(统计) 条形图: 特点:绘制离散的数据,能够一眼 ...