由于在HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,因此 ,对于HotSpot来 说 ,虽然-Xoss参数 (设置本地方法栈大小)存在 ,但实际上是无效的,栈容量只由-Xss参数设定。 关于虚拟机栈和本地方法栈,在Java虚拟机规范中描述了两种异常:
这里把异常分成两种情况,看似更加严谨,但却存在着一些互相重叠的地方:当栈空间无法继续分配时,到底是内存太小,还是已使用的栈空间太大,其本质上只是对同一件事情的两种描述而已。
在笔者的实验中,将实验范围限制于单线程中的操作,尝试了下面两种方法均无法让虚拟机产生OutOfMemoryError异常 ,尝试的结果都是获得StackOverflowError异 常 ,测试代码如代码清单2所示。
代码清单2 虚拟机找和本地方法栈OOM测试(仅作为第1点测试程序)
运行结果:
stack length:17650
Exception in thread “main” java.lang.StackOverflowError
at Chapter_02.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
at Chapter_02.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
…..后续异常堆栈信息省略
实验结果表明:在单个线程下,无论是由于栈帧(一个方法中包含的本地变量数)太大还是虚拟机栈容量(-Xss参数减少每个线程栈内存容量)太小,当内存无法分配的时候,虚拟机拋出的都是StackOverflowError异常。
如果测试时不限于单线程,通过不断地建立线程的方式倒是可以产生内存溢出异常,如代码清单2-5所示。但是这样产生的内存溢出异常与栈空间是否足够大并不存在任何联系,或者准确地说,在这种情况下,为每个线程的栈分配的内存越大,反而越容易产生内存溢出异常。
其实原因不难理解,操作系统分配给每个进程的内存是有限制的,譬如32位的Windows 限制为2GB。虚拟机提供了参数来控制Java堆和方法区的这两部分内存的最大值。剩余的内存为2GB ( 操作系统限制)减去Xmx ( 最大堆容量),再减去MaxPermSize (最大方法区容量 ),程序计数器消耗内存很小,可以忽略掉。如果虚拟机进程本身耗费的内存不计算在内 ,剩下的内存就由虚拟机栈和本地方法栈“瓜分” 了。每个线程分配到的栈容量越大,可以建立的线程数量自然就越少,建立线程时就越容易把剩下的内存耗尽。
这一点读者需要在开发多线程的应用时特别注意,出现StackOverflowError异常时有错误堆栈可以阅读,相对来说,比较容易找到问题的所在。而且 ,如果使用虚拟机默认参数,栈深度在大多数情况下(因为每个方法压入栈的帧大小并不是一样的,所以只能说在大多数情况下)达到1000〜2000完全没有问题,对于正常的方法调用(包括递归),这个深度应该完全够用了。但是 ,如果是建立过多线程导致的内存溢出,在不能减少线程数或者更换64位虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取更多的线程。如果没有这方面的处理经验,这种通过“减少内存”的手段来解决内存溢出的方式会比较难以想到。
代码清单3 创建线程导致内存溢出异常
Java是一门面向对象的编程语言,在Java编程运行过程中无时无刻都有被创建出来。在语言层面上,创建对象(例如克隆、反序列化)通常仅仅是一个new关键字而已,而在虚拟机中,对象(文中讨论的对象限于普通Java对象,不包括数组和Class对象等)的创建又是怎样一个过程呢?
虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可完全确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。假设Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存另一边,中间放着一个指针作为分界点的指示器,这种分配方式称为“指针碰撞”(Bump the Pointer)。如果Java堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表(Free List)”。选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。因此,在使用Serial、ParNew等带有Compact过程的收集器时,系统采用的分配算法是指针碰撞,而使用CMS这种基于Mark-Sweep算法的收集器时,通常采用空闲列表。
除如何划分可用空间之外,还有另外一个需要考虑的问题是对象创建在虚拟机中是非常频繁的行为,即使是仅仅修改一个指针所指向的位置,在并发情况下也并不是线程安全的,可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况。解决这个问题有两种方案,一种是对分配内存空间的动作进行同步处理————实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;另一种是把内存的动作按照线程划分在不同额空间之中进行,即每个线程在Java堆中预先分配一块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。哪个线程要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的FLAB时,才需要同步锁定。虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来设定。
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),如果使用TLAB,这一工作过程也可以提前至TLAB分配时进行。这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
接下来,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头(Object Header)之中。根据虚拟机当前的运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从Java程序的视角来看,对象创建才刚刚开始————
下面代码清单1是HotSpot虚拟机bytecodeInterpreter.cpp中的代码片段(这个解释器实现很少有机会实际使用,因为大部分平台了都使用模版解释器;当代码通过JIT编译器执行是差异就更大。不过,这段代码用于了解HotSpot的运行过程是没有什么问题的)。
代码清单1 HotSpot解释器的代码片段
HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
HotSpot虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等,这部分数据的长度在32位和64位的虚拟机(暂不考虑开启压缩指针的场景)中分别为32个和64个Bits,官方称它为“Mark Word”。对象需要存储的运行时数据很多,其实已经超出了32、64位Bitmap结构所能记录的限度,但是对象头信息是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息,它会根据对象的状态复用自己的存储空间。例如在32位的HotSpot虚拟机中对象未被锁定的状态下,Mark Word的32个Bits空间中的25Bits用于存储对象哈希码(HashCode),4Bits用于存储对象分代年龄,2Bits用于存储锁标志位,1Bit固定为0,在其他状态(轻量级锁定、重量级锁定、GC标记、可偏向)下对象的存储内容如下表1所示。
表1 HotSpot虚拟机对象头Mark Word
存储内容 | 标志位 | 状态 |
---|---|---|
对象哈希码、对象分代年龄 | 01 | 未锁定 |
指向锁记录的指针 | 00 | 轻量级锁定 |
指向重量级锁的指针 | 10 | 膨胀(重量级锁定) |
空,不需要记录信息 | 11 | GC标记 |
偏向线程ID、偏向时间戳、对象分代年龄 | 01 | 可偏向 |
对象头的另外一部分是类型指针,即是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说查找对象的元数据信息并不一定要经过对象本身,这点我们在下一节讨论。另外,如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中无法确定数组的大小。
代码清单2是HotSpot虚拟机markOop.cpp中的代码(注释)片段,它描述了32bits下MarkWord的存储状态。
代码清单2 markOop.cpp片段
接下来实例数据部分是对象真正存储的有效信息,也既是我们在程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的都需要记录袭来。这部分的存储顺序会受到虚拟机分配策略参数(FieldsAllocationStyle)和字段在Java源码中定义顺序的影响。HotSpot虚拟机默认的分配策略为longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers),从分配策略中可以看出,相同宽度的字段总是被分配到一起。在满足这个前提条件的情况下,在父类中定义的变量会出现在子类之前。如果CompactFields参数值为true(默认为true),那子类之中较窄的变量也可能会插入到父类变量的空隙之中。
第三部分对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是对象的大小必须是8字节的整数倍。对象头部分正好似8字节的倍数(1倍或者2倍),因此当对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。
建立对象是为了使用对象,我们的Java程序需要通过栈上的reference数据来操作堆上的具体对象。由于reference类型在Java虚拟机规范里面只规定了是一个指向对象的引用,并没有定义这个引用应该通过什么种方式去定位、访问到堆中的对象的具体位置,对象访问方式也是取决于虚拟机实现而定的。主流的访问方式有使用句柄和直接指针两种。
这两种对象访问方式各有优势,使用句柄来访问的最大好处就是reference中存储的是稳定句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要被修改。
使用直接指针来访问最大的好处就是速度更快,它节省了一次指针定位的时间开销,由于对象访问的在Java中非常频繁,因此这类开销积小成多也是一项非常可观的执行成本。从上一部分讲解的对象内存布局可以看出,就虚拟机HotSpot而言,它是使用第二种方式进行对象访问,但在整个软件开发的范围来看,各种语言、框架中使用句柄来访问的情况也十分常见。
]]>这些区域有各自的用途,及创建和销毁的时间,有些区域随虚拟机进程的启动而存在,有些区域则是依赖用户线程的启动和结束来建立和销毁。Java虚拟机所管理的内存包括以下几个运行时数据区域,如图1所示。
程序计数器(Program Counter Resister)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转异常处理、线程恢复等基层功能都需要依赖这个计数器完成。
由于Java虚拟机的多线程是通过线程轮流切换并分配处理执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器说是一个内核)都只执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为”线程私有”的内存。
如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器则为空(Undefined)。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
经常有人把Java内存区分为堆内存(Heap)和栈内存(Stack),这种分法比较粗糙,Java内存区域的划分实际上远比这复杂。这种划分方式的流行只能说明大多程序员最关注的、与对象内存分配关系最密切的内存区域是这两块。其中所指的“堆”将在下面进行专门的讲述,而所指的“栈”就是现在讲的虚拟机栈,或者说是虚拟机栈中局部变量表部分。
局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、int、short、long、float、double、char)、对象引用(reference类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。
其中64位长度的long和double类型的数据会占用2个局部变量空间(Slot),其余的数据类型只占用1个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多个的局部变量空间是完全正确的,在方法运行期间不会改变局部变量表的大小。
在Java虚拟机规范中,对这个区域规定了两种异常情况:如果线程请求的栈深读大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展(当前大部分的Java虚拟机都可以动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。
本地方法栈(Native Method Shack)与虚拟机栈所发挥的作用非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。在虚拟机规范中对本地方法栈中方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法也可以抛出StackOverflowError和OutOfMemorryError异常。
对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配,但随着JIT编译的发展与逃逸分析技术逐渐成熟,栈上分配、变量替换优化技术将会导致一些微妙的变化,所有的对象都分配在堆上也渐渐变得不是那么“绝对”了。
Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”。从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以Java堆中还可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。从内存分配的角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区。不过无论如何划分,都与存放内容无关,无论哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好地回收内存,或者更快的分配内存。
根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实际时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
方法区(Method Area)和Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器后的代码等数据。虽然Java虚拟机范围把方法描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。
对于习惯在HotSpot虚拟机上开发、部署程序的开发者来说,很多人都更愿意把方法却有称为“永久代”(Permanent Generation),本质上两者并不等价,仅仅是因为HotSpot虚拟机的设计团队选择把GC分代收集扩展到方法区,或者说使用永久代来实现方法区而已,这样HotSpot的垃圾收集器可以像管理Java堆一样管理这部分内存,能够省去专门为方法区编写内存管理代码的工作。对于其他虚拟机(如BEA JRockit、IBM J9等)来说是不存在永久代的概念的。原则上,如何实现方法区属于虚拟机实现细节,不受虚拟机规范约束,但使用永久代来实现方法区,现在看来并不是一个好主意,因为这样容易遇到内存溢出问题(永久代有-XX:MaxPermSize的上限,J9和JRockit只要没有触碰到进程可用内存的上限,例如32位系统中的4GB,就不会出现问题),而且有极少数方法(例如String.intern())会因为这个原因导致不同虚拟机下有不同的表现。因此,对于HotSpot虚拟机,根据官方发布的路线图信息,现在也有放弃永久代并逐步改为采用Native Memory来实现方法区的规划了,在目前已经发布的JDK1.7的HotSpot中,已经把原本放在永久代的字符串常量池移出。
Java虚拟机规范对方法区的限制非常宽松,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。相对而言,垃圾收集行为在这区域是比较少出现的,但并非数据进入方法区就如永久代的名字一样“永久”存在了。这区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说,这个区域的回收确实是必要的。在Sun公司的BUG列表中,曾出现过若干个严重的BUG就是由于低版本的HotSpot虚拟机对此区域未完全回收而导致内存泄漏。
根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
运行时常量池(Runtime Constant Pool)是方法去的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
Java虚拟机对Class文件没一部分(自然也包括常量池)的格式都有严格规定,每一个字节用于存储哪种数据都必须符合规范上的要求才会被虚拟机认可、装载和执行,但对于运行时常量池,Java虚拟机规范没有做任何细节的要求,不同的提供商实现的虚拟机可以按照自己的需要来实现这个内存区域。不过,一般来说,除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。
运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。
既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致OutOutMemoryError异常出现,所以我们放到这里一起讲解。
在JDK1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。
显然,本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,肯定还是会受到本机总内存(包括RAM以及SWAP区或者分页文件)大小以及处理器寻找空间的限制。服务器管理员在配置虚拟机参数时,会根据实际内存设置-Xmx等参数信息,但经常忽略直接内存,使得各个内存区域总和大于物理内存限制(包括物理和操作系统级的限制),从而导致动态扩展时出现OutOfMemoryError异常。
在Java多线程中,可以使用synchronized关键字实现线程之间同步互斥,但在JDK1.5中增加了ReentranLock类也能达到同样的效果,并且在扩展功能上也更加强大,比如具有嗅探锁定,多路分支通知等功能,而且在使用上也比synchronized更加的灵活。
既然ReentranLock类在功能上相比synchronized更多,那么就以一个初步的程序示例来介绍一下ReentranLock类的使用。
代码清单1 测试1
运行结果如图1所示,从运行结果来看,当前线程打印完毕之后将锁将进行释放,其他线程才可以继续打印。线程打印的数据是分组打印,因为当前线程已经持有锁,但线程之间的打印顺序是随机的。
Lock下lock()持有对象监听作用。
代码清单2 测试2
运行结果如图2所示,,调用lock.lock()代码的线程就持有了“对象监视器”,其他线程只有等待锁被释放时再次抢夺。效果和使用synchronized关键字一样,线程之间还是顺序执行的。
关键字synchronized与wait()和notify()/notifyAll()方法相结合可以实现等待/通知模式,类ReentrantLock也可以实现同样的功能,但需要借助于Condition对象。Condition类是在JDK5中出现的技术,使用它有更好的灵活性,比如可以实现多路通知功能,也就是在一个Lock对象里面可以创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择性地进行线程通知,在调度线程上更加灵活。
在使用notify()/notifyAll()方法进行通知时,被通知的线程却是由JVM随机选择的。但使用Reentrant Lock结合Condition类是可以实现前面介绍过的“选择性通知”,这个功能是非常重要的,而且在Condition类中是默认提供的。
而synchronized就相当于整个Lock对象中只有一个单一的Condition对象,所有的线程都注册在它一个对象的身上。线程开始notifyAll()时,需要通知所有的WAITING线程,没有选择权,会出现相当大的效率问题。
代码清单3 condition错误用法
运行结果如图3所示,出现异常没有监视对象。
报错的异常信息是监视器出错,解决的办法是必须在condition.await()方法调用之前调用lock.lock()代码获得同步监视器。
代码清单4 获得同步监听
运行结果如图4所示,控制台中只打印一个字母A,原因是调用了Condition对象的await()方法,使当前执行任务的线程进入等待WAITING状态。
代码清单5 实现等待通知
程序运行结果如图5所示,成功实现等待/通知模式。
Object类中的wait()方法相当于Condition类中的await()方法。
Object类中的wait(long timeout)方法相当于Condition类中await(long time,TimeUnit unit)方法。
Object类中的notify()方法相当于Condition类中的signal()方法。
Object类中的notifyAll()方法相当于Condition类中的signalAll()方法。
其实Condition对象也可以创建多个。那么一个Condition对象和多个Condition对象在使用上有什么区别呢?
代码清单6 错误用法
运行结果如图6所示,A和B线程都被唤醒了。
如果想单独唤醒部分线程该怎么处理呢?这时就有必要使用多个Condition对象了,也就是Condition对象可以唤醒部分指定线程,有助于提升程序运行效率。可以先对线程进行分组,然后再唤醒指定组中的线程。
代码清单7 正确用法
运行结果如图7所示,只有线程A被唤醒了。使用ReentrantLock对象可以唤醒指定种类的线程,这是控制部分线程行为的方便方式。
代码清单8 一对一交替打印
运行结果如图8所示,通过使用Condition对象,成功实现交替打印的结果。
代码清单9 多对多交替打印
运行结果如图9所示,程序运行后出现假死。可以使用signalAll()方法来解决。将MyService.Java类中两处signal()代码改成signalAll()后,运行结果如图10所示,程序正常运行,不再出现假死状态。
“打印★”和“打印☆”是交替输出的,但是“有可能★★连续”和“有可能☆☆连续”却不是交替输出的,有时候出现连续打印的情况。原因是程序中使用一个Condition对象,再结合signalAll()方法来唤醒所有的线程,那么唤醒的线程就有可能是同类,所以就出现连续打印“有可能★★连续”和“有可能☆☆连续”的情况了。
公平与非公平锁:锁lock分为“公平锁”和“非公平锁”,公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,及先来先得的FIFO先进先出的顺序。而非公平锁就是一种获取锁的抢占机制,是随机获得锁的,和公平锁不一样的就是先来的不一定先得到锁,这个方式可能造成某些线程一直拿不到锁,结果也就是不公平的了。
代码清单10 公平锁与非公平锁
公平锁运行结果如图11所示,打印的结果基本是呈有序的状态,这就是公平锁的特点。
非公平锁运行结果如图12所示,结果基本都是乱序的,说明先start()启动的线程不代表先获得锁。
1)方法int getHoldCount()的作用是查询当前线程保持此锁定的个数,也就是调用lock()方法的次数。
代码清单11 getHoldCount()方法
运行结果如图13所示,分别查询当前线程保持此锁定的个数。
2)方法int getQueueLength()的作用是返回正等待获取此锁定的线程估计数,比如有5个线程,一个线程首先执行await()方法,那么在调用getQueueLength()方法后返回值是4,说明有4个线程同时在等待lock的释放。
代码清单12 getQueueLength()方法
允许结果如图14所示。
3)方法int getWaitQueueLength(Condition condition)的作用是返回等待此锁定相关的给定条件Condition的线程估计数,比如5个线程,每个线程都执行了同一个condition对象的await()方法,则调用getWaitQueueLength(Condition condition)方法时返回的int值是5。
代码清单13 getWaitQueueLength(Condition condition)
运行结果如图15所示。
1)方法boolean hasQueueThread(Thread thread)的作用是查询指定的线程是否正在等待获取此锁定。
代码清单14 hasQueuedThread()方法
运行结果如图16所示。
2)方法boolean hasWaiters(Condition condition)的作用是查询是否有线程正在等待与此锁定有关的condition条件。
代码清单15 hasWaiters(Condition condition)方法
运行结果如图17所示。
1)方法boolean isFair()的作用是判断是不是公平锁。
代码清单16 isFair()方法
运行结果如图18所示,在默认情况下,ReentrantLock类使用的是非公平锁。
2)方法boolean isHeldByCurrentThread()的作用是查询当前线程是否保持此锁定。
代码清单17 isHeldByCurrentThread()方法
运行结果如图19所示。
3)方法boolean isLocked()的作用是查询此锁定是否任意线程保持。
代码清单18 isLocked()方法
运行结果如图20所示。
延迟加载/“懒汉模式”解析是在调用方法时实例才被创建。
代码清单1 懒汉模式
运行结果如图1所示,虽然取得一个对象的实例,但如果是在多线程的环境中,就会出现取出多个实例的情况,与单例模式的初衷是相背离的。
前面的两个测试虽然使用了“立即加载”和“延迟加载”实现了单例设计模式,但在多线程的环境中,前面“延迟加载”示例中的代码完全就是错误的,根本不能实现保存单例的状态。来看一下如何在多线程环境中结合“错误的单例模式”创建出“多例”。
代码清单2 “多例”
运行结果如图2所示,打印出来了3种hasCode,说明创建出了3个对象,并不是单例的,这就是“错误的单例模式”。
在代码清单2中public static MyObject getInstance()
前加上synchronized。运行结果如图3所示。
此方法加入同步synchronized关键字得到相同实例的对象,但此方法的运行效率非常低下,是同步运行的,下一个线程想要取得对象,则必须等上一个线程释放锁之后,才可以继续运行。
同步方法是对方法的整体进行持锁,这对运行效率来讲是不利的。改成同步代码块能解决吗?
代码清单3 代码块
运行结果如图4所示,此方法加入同步synchronized语句块得带相同实例的对象,但此方法的运行效率也是非常低的,和synchronized同步方法一样是同步运行的。继续更改代码尝试解决这个缺点。
同步代码块可以针对某些重要的代码进行单独的同步,而其他的代码则不需要同步。这样在运行时,效率完全可以得到大幅提升,对代码清单4中的代码进行修改。
代码清单5 修改MyObject类
运行结果如图5所示,此方法使用同步synchronized语句块,只对实例化对象的关键代码进行同步,从语句的结构来讲,运行的效率的确得到了提升。但如果是遇到多线程的情况下还是无法解决得到同一个实例对象的结果。到底如何解决“懒汉模式”遇到多线程的情况呢?
在最后的步骤中,使用的是DCL双检查锁机制来实现多线程环境中的延迟加载单例设计模式。对代码清单4中的代码进行修改。
代码清单6 修改MyObject类
运行结果如图6所示,使用双重检查锁功能,成功地解决了“懒汉模式”遇到多线程的问题。DCL也是大多数线程结合单例模式使用的解决方案。
什么是立即加载?立即加载就是使用类的时候已经将对象创建完毕,常见的实现办法就是直接new实例化。而立即加载从中文的语境来看,有“着急”、”急迫”的含义,所以也称为“饿汉模式”。
立即加载/“饿汉模式”是在调用方法前,实例已经被创建了。
代码清单1 饿汉模式
运行结果如图1所示,打印的hashCode是同一个值,说明对象是同一个,也就实现了立即加载型单例设计模式。
什么是延迟加载?延迟加载就是字啊调用get()方法时实例才被创建,常见的实现方法就是在get()方法中进行new实例化。而延迟加载从中文的语境来看,是“缓慢”、“不急迫”的含义,所以也称为“懒汉模式”。
]]>在JDK库中Timer类主要负责计划任务的功能,也就是在指定的时间开始执行某一个任务。Timer类的方法列表如图1所示。
Timer类的主要作用就是设置计划任务,但封装任务的类却是TimerTask类,类结构如图2所示。执行计划任务的代码要放入TimerTask的子类中,因为因为TimerTask是一个抽象类。
该方法的作用是在指定的日期执行一次某一任务。
1 执行任务的时间晚于当前的时间:在未来执行的效果
代码清单1 在未来执行的效果
程序运行后的结果如图3所示。任务虽然执行完了,但进程还未销毁,为什么会出现这样的情况?
在创建Timer对象时,JDK源码如下:
此构造方法调用的是如下构造方法:
查看构造方法可以得知,创建一个Timer就是启动一个新的线程,这个新启动的线程并不是守护线程,它一直在运行。
查看构造方法可以得知,创建一个Timer就是启动一个新的线程,这个新启动的线程并不是守护线程,它一直在运行。
代码清单2 Timer的守护线程
程序运行后迅速结束当前的进程,并且TimerTask中的任务不再被运行,因为线程已经结束了。
2 计划时间早于当前时间:提前运行的效果
如果执行任务的时间早于当前时间,则立即执行task任务。将代码清单2中//Timer timer=new Timer();
注释去掉。
3 多个TimerTask任务及延时的测试
Timer中允许有多个TimerTask任务。
代码清单3 多个TimerTask任务
程序运行结果如图6所示。
TimerTask是以队列的方式一个一个被顺序执行的,所以执行的时间有可能和预期的时间不一致,因为前面的任务有可能消耗的时间较长,则后面的任务运行的时间也会被延迟。
代码清单4 任务运行被延迟
由于task1需要用时20秒执行完成任务,task1开始的时间是2017-11-14 10:30:18,那么将要影响task2的计划任务的时间,task2以此时间为基准,向后延迟20秒,task2在11:33:20执行任务。因为Task是被放入队列中的,得一个一个顺序运行。
程序运行结果如图6所示。
该方法的作用是指在指定日期之后,按指定的间隔周期性地无限循环执行某一任务。
1 计划时间晚于当前时间:在未来执行的效果。
代码清单5 在未来的时间开始循环执行
运行结果如图8所示,每隔4秒运行一次TimerTask,并且无限期地重复执行。
2 计划时间早于当前时间:提前运行的效果
如果计划时间早于当前时间,则立即执行task。
运行代码清单5中程序,运行结果如图9所示。
3 任务执行时间被延迟时
代码清单6 任务执行时间被延迟
运行结果如图10所示,任务被延时了但还是一个一个顺序运行。
4 TimerTask类的cancel()方法
TimerTask类中的cancel()方法的作用是将自身从任何队列中清楚。
代码清单7 TimerTask类的cancel()方法
TimerTask类的cancel()方法是将自身从任务队列中被移除,其他任务不受影响。
5 Timer类的cancel()方法
和TimerTask类中的cancel()方法清楚自身不同,Timer类中的cancel()方法的作用是将任务队列中的全部任务清空。
代码清单8 Timer类的cancel()方法
运行结果如图12所示,全部任务被清除,并且进程被销毁,按钮有红色变为灰色。
6 Timer的cancel()方法注意事项
Timer类中的cancel()方法有时并不一定会停止执行计划任务,而是正常执行。
代码清单9 cancel不一定能停止执行计划
运行结果如图13所示。这是因为Timer类中的cancel()方法有时并没有争抢到queue锁,所以TimerTask 正常运行。
该方法的作用是以执行schedule(TimerTask task,long delay)方法当前的时间为参考时间,在此时间基础上延迟指定的毫秒数后执行一次TimerTask任务。
代码清单10 延迟执行一次
任务task被延迟7秒执行,如图14所示。
该方法的作用是以执行schedule(TimerTask task,long delay,long period)方法当前的时间Wie参考时间,在此时间基础上延迟指定的毫秒数,再以某一间隔时间无限次数地执行某一任务。
代码清单11 无限循环
凡是使用方法带有period参数的,都是无限循环执行TimerTask中的任务。
方法schedule和方法scheduleAtFixedRate都会按顺序执行,所以不要考虑非线程安全的情况。
方法schedule和scheduleAtFixedRate主要的区别只在于不延时的情况。
使用schedule方法:如果执行任务的时间没有被延迟时,那么下一次任务的执行时间参考的是上一次任务的“开始”时的时间来计算。
使用scheduleAtFixedRate方法:如果执行任务的时间没有被延迟时,那么下一次任务的执行时间参考的是上一次任务的“结束”时的时间来计算。
延时的情况则没有区别,也就是使用schedule或scheduleAtFixedRate方法都是如果执行任务的时间被延迟时,那么下一次任务的执行时间参考的是上一次任务“结束”时的时间来计算。
1 测试schedule方法任务不延时
代码清单12 测试schedule方法任务不延时
控制台打印的结果证明,在不延时的情况下,如果执行任务的时间没有被延迟时,则下一次执行任务的时间是上一次任务的开始时间加上delay时间。
2 测试schedule方法任务超时
代码清单13 schedule方法任务超时
程序运行结果如图17所示,从控制台打印的结果来看,如果执行任务的时间被延迟时,那么下一次的执行时间以上一次“结束”时的时间为参数来计算。
3 测试scheduleAtFixedRate方法任务不延时
代码清单14 scheduleAtFixedRate方法任务不延时
运行结果如图18所示,控制台打印的结果证明,如果执行任务的时间没有被延迟,则下一次执行任务的时间是上一次任务的开始时间加上delay时间。
4 测试scheduleAtFixedRate方法任务超时
代码清单15 scheduleAtFixedRate方法任务超时
运行结果如图19所示,如果执行任务的时间被延迟,那么下一次任务的执行时间以上一次任务“结束”时的时间为参考来计算。
5 验证schedule方法不具有追赶执行性
代码清单16 schedule方法不具有追赶执行性
运行结果如图20所示,时间“2017-11-14 15:13:43”到“2017-11-14 15:16:06”之间的时间所对应的Task任务被取消了,不执行了。这就是Task不追赶的情况。
6 验证scheduleAtFixedRate方法具有追赶执行性
代码清单17 scheduleAtFixedRate方法具有追赶执行性
运行结果如图21所示,两个时间段内所对应的Task被“补充性”执行了,这就是Task任务追赶执行的特性。
线程与线程之间不是独立的个体,它们彼此之间可以相互通信和协作。
使用sleep()结合while(true)死循环法来实现多线程间通信。
代码清单1 sleep()结合while(true)实现通信
运行结果如图1所示。虽然两个线程之间实现了通信,但有一个弊端就是,线程ThreadB.java不停地通过while语句轮询机制来检测某一个条件,这样会浪费CPU资源。
如果轮询的时间间隔很小,更浪费CPU资源;如果轮询的时间间隔很大,有可能会取不到想要得到的数据。所以就需要有一种机制来实现减少CPU的资源浪费,而且还可以实现多个线程间通信,它就是“wait/notify”机制。
对于通过多个线程共同访问同一个变量,可以实现多个线程之间通信,但这种通信机制不是“等待/通知”,两个线程完全会主动式地读取一个共享变量,在花费时间的基础上,读到的值是不是想要的,并不能完全确定。所以现在迫切需要一种“等待/通知”机制来满足的需求。
方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait()所在的代码行处停止执行,直到接到通知或被中断为止。在调用wait()之前,线程必须或得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。在执行wait()方法后,当前线程释放锁。在从wait()返回前,线程与其它线程竞争重新或得锁。如果调用wait()时没有持有适当的锁,则抛出IllegalMonitorStateException,它是RuntimeException的一个子类,因此,不需要try-catch语句进行辅捉异常。
方法notify()也要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁。如果调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选其中一个呈wait状体的线程,对其发出通知notify,并使它等待获取该对象的对象锁。需要说明的是,在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状体的线程也并不能马上获取该对象锁,要等待到执行notify()方法的线程将程序执行完毕,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。当第一个锁得到该对象锁的wait线程执行完毕以后,它会释放该对象锁,此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程才可以获取该对象锁。当第一个获得了该对象的wait线程运行完毕后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,还会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll。
用一句话总结一下wait和notify:wait使线程停止运行,而notify使停止的线程继续运行。
如果在某类中有Date类型的属性,数据库中存储可能是’yyyy-MM-dd hh:MM:ss’要在查询时获得年月日,在该属性上标注@Temporal(TemporalType.DATE) 会得到形如’yyyy-MM-dd’ 格式的日期。
如果在某类中有Date类型的属性,数据库中存储可能是’yyyy-MM-dd hh:MM:ss’要获得时分秒,在该属性上标注 @Temporal(TemporalType.TIME) 会得到形如’HH:MM:SS’ 格式的日期。
如果在某类中有Date类型的属性,数据库中存储可能是’yyyy-MM-dd hh:MM:ss’要获得’是’yyyy-MM-dd hh:MM:ss’,在该属性上标注 @Temporal(TemporalType.TIMESTAMP) 会得到形如’HH:MM:SS’ 格式的日期
如果不是在多继承的情况下,使用继承Thread类和实现Runnable接口在取得程序运行的结果上并没有什么太大的区别。如果一旦出现“多继承”的情况,则用实现Runnable接口的方式来处理多线程的问题就是很有必要的。
代码清单1 死循环
程序开始运行后,根本停不下来,结果如图1所示。
停不下来的原因主要就是main线程一直在处理while()循环,导致程序不能运行后面的代码。
采用多线程,解决死循环。
代码清单2 解决死循环
运行结果如图2,但当上面的代码运行在-server服务器模式中64bit的JVM时,会出现死循环。解决的办法是使用volatile关键字。
关键字volatile的作用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。
在探讨volatile之前先做一个测试。
代码清单3 JVM设置为 -Server
在win10结合JDK64bit的环境中,使用Eclipse开发环境运行后的结果如图3所示。
但是如果使用同样的代码,让他们运行在JVM设置为Server服务器的环境中,设置如图4所示。
运行结果如图5所示,代码System.out.println("线程被停止了!");
从未被执行。
是什么样的原因造成将JVM设置为-server使出现死循环呢?在启动RunThread.java线程时,变量private boolean isRunning = true;
存在于公共堆栈及线程的私有堆栈中。在JVM被设置为-server模式时为了线程运行的效率,线程一直在私有堆栈中取得isRunning的值为true。而代码thread.setRunning(false);
虽然被执行,更新的却是公共堆栈中的isRunning变量值false,所以一直就是死循环的状态。内存结果如图6所示。
这个问题其实就是私有堆栈中的值和公共堆栈中的值不同步造成的。解决这样的问题就要使用volatile关键字,它主要的作用就是当线程访问isRunning这个变量时,强制性从公共堆栈中进行取值。
在代码清单3中,定义变量isRunning前加上volatile,运行结果如图7.
通过使用volatile关键字,强制的从公共内存中读取变量的值,内存结构如图8所示。
使用volatile关键字增加了实例变量在多个线程之间的可见性。但volatile关键字最致命的缺点是不支持原子性。
下面将关键字synchronized和volatile进行一下比较:
线程安全包含原子性和可见性两个方面,Java的同步机制都是围绕这两个方向来确保线程安全的。
关键字volatile虽然增加了实例变量在多个线程之间的可见性,但它却不具备同步性,那么也就不具备原子性。
]]>POJO(Plain Ordinary Java Object)简单的Java对象,实际就是普通JavaBeans,是为了避免和EJB混淆所创造的简称。
使用POJO名称是为了避免和EJB混淆起来, 而且简称比较直接. 其中有一些属性及其getter、setter方法的类,没有业务逻辑,有时可以作为VO(value -object)或dto(Data Transform Object)来使用.当然,如果你有一个简单的运算属性也是可以的,但不允许有业务方法,也不能携带有connection之类的方法。
POJO对象有时也被称为Data对象,大量应用于表现现实中的对象。如果项目中使用了Hibernate框架,有一个关联的xml文件,使对象与数据库中的表对应,对象的属性与表中的字段相对应。
POJO 和JavaBean是我们常见的两个关键字,一般容易混淆,POJO全称是Plain Ordinary Java Object / Pure Old Java Object,中文可以翻译成:普通Java类,具有一部分getter/setter方法的那种类就可以称作POJO,但是JavaBean则比 POJO复杂很多, Java Bean 是可复用的组件,对 Java Bean 并没有严格的规范,理论上讲,任何一个 Java 类都可以是一个 Bean 。但通常情况下,由于 Java Bean 是被容器所创建(如 Tomcat) 的,所以 Java Bean 应具有一个无参的构造器,另外,通常 Java Bean 还要实现 Serializable 接口用于实现 Bean 的持久性。 Java Bean 是不能被跨进程访问的。
JavaBean是一种组件技术,就好像你做了一个扳子,而这个扳子会在很多地方被拿去用,这个扳子也提供多种功能(你可以拿这个扳子扳、锤、撬等等),而这个扳子就是一个组件。一般在web应用程序中建立一个数据库的映射对象时,我们只能称它为POJO。POJO(Plain Old Java Object)这个名字用来强调它是一个普通java对象,而不是一个特殊的对象,其主要用来指代那些没有遵从特定的Java对象模型、约定或框架(如EJB)的Java对象。理想地讲,一个POJO是一个不受任何限制的Java对象(除了Java语言规范)。
POJO是一个简单的普通的Java对象,它不包含业务逻辑或持久逻辑等,但不是JavaBean、EntityBean等,不具有任何特殊角色和不继承或不实现任何其它Java框架的类或接口。
]]>ifconfig
我本地网卡为eno16777736vi /etc/sysconfig/network-scripts/ifcfg-eno16777736
BOOTPROTO=static
指定地址的获取方式IPADDR=192.168.0.225
ip地址NETMASK=255.255.255.0
子网掩码GATEWAY=192.168.0.1
网关systemctl restart network
ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=45 time=241 ms
|
|
启动tomcat后,可以进入后台,如图1.
第二步:修改ip访问权限
对于IP访问权限在设置在/tomcat/webapps/manager/META-INF/context.xml中。
早期版本Tomcat默认是没有限制的,如图2所示。
现在Tomcat对于默认是对IP进行限制的,如果不需要进行限制可以模仿早期Tomcat默认配置,去掉IP限制,
默认如图3所示。
第三步:在pom文件中配置tomcat插件。
第四步:部署
初次部署可以使用 “tomcat7:deploy” 命令
如果已经部署过使用 “tomcat7:redeploy” 命令
(如果第一次部署到根目录,可以直接用“tomcat7:redeploy”)
启动过程如图4、图5.
所谓网络地址的重用表现在两个方面:
|
|
不管在使用Socket类连接服务器时是直接使用IP和端口,还是使用SocketAddress,这两个方法都返回SocketAddress形式的网络地址。当Socket对象未连接时这两个方法返回null,但要注意的是只有在Socket对象未连接时这两个方法才返回null,而当已经连接成功的Socket对象关闭后仍可使用这两个方法得到相应的网络地址。
虽然上面曾多次提到SocketAddress,但SocketAddress只是个抽象类,它除了有一个默认的构造方法外,其它的方法都是abstract的,因此,我们必须使用SocketAddress的子类来建立SocketAddress对象。在JDK1.4中J只为我们提供了IP网络地址的实现类:java.net.InetSocketAddress。这个类是从SocketAddress继承的,我们可以通过如下的方法来建立SocketAddress对象。
|
|
下面的代码演示了如何通过SocketAddress来共享网络地址:
|
|
输出结果:
服务器域名:www.ptpress.com.cn
服务器IP:219.238.168.74
服务器端口:80
本地IP:192.168.18.253
本地端口:4250
如果多次运行后,本地端口的值可能在每次都不一样。这是因为在socket2在连接时并未使用bind来绑定本地的端口,而这个本地端口是由系统在1024至65,535中随机选取的,因此,在每次运行程序时这个本地端口不一定相同。
本文出自 “李宁的极客世界”博客, http://androidguy.blog.51cto.com/974126/214448
]]>Bean的中文含义是“豆子”,顾名思义,JavaBean是指一段特殊的Java类,
就是有默然构造方法,只有get,set的方法的java类的对象.
专业点解释是:
JavaBean定义了一组规则
JavaBean就是遵循此规则的平常的Java对象
满足这三个条件:
1.实现序列化接口
2.提供无参数的构造器
3.提供getter 和 setter方法访问它的属性.
简单地说,JavaBean是用Java语言描述的软件组件模型,其实际上是一个类。这些类遵循一个接口格式,以便于使函数命名、底层行为以及继承或实现的行为,可以把类看作标准的JavaBean组件进行构造和应用。
JavaBean一般分为可视化组件和非可视化组件两种。可视化组件可以是简单的GUI元素,如按钮或文本框,也可以是复杂的,如报表组件;非可视化组件没有GUI表现形式,用于封装业务逻辑、数据库操作等。其最大的优点在于可以实现代码的可重用性。JavaBean又同时具有以下特性。
对于有过其他语言编程经验的读者,可以将其看作类似微软的ActiveX的编程组件。但是区别在于JavaBean是跨平台的,而ActiveX组件则仅局限于Windows系统。总之,JavaBean比较适合于那些需要跨平台的、并具有可视化操作和定制特性的软件组件。
JavaBean组件与EJB(Enterprise JavaBean,企业级JavaBean)组件完全不同。EJB 是J2EE的核心,是一个用来创建分布式应用、服务器端以及基于Java应用的功能强大的组件模型。JavaBean组件主要用于存储状态信息,而EJB组件可以存储业务逻辑。
程序中往往有重复使用的段落,JavaBean就是为了能够重复使用而设计的程序段落,而且这些段落并不只服务于某一个程序,而且每个JavaBean都具有特定功能,当需要这个功能的时候就可以调用相应的JavaBean。从这个意义上来讲,JavaBean大大简化了程序的设计过程,也方便了其他程序的重复使用。
JavaBean传统应用于可视化领域,如AWT(窗口工具集)下的应用。而现在,JavaBean更多地应用于非可视化领域,同时,JavaBean在服务器端的应用也表现出强大的优势。非可视化的JavaBean可以很好地实现业务逻辑、控制逻辑和显示页面的分离,现在多用于后台处理,使得系统具有更好的健壮性和灵活性。JSP + JavaBean和JSP + JavaBean + Servlet成为当前开发Web应用的主流模式。
在程序设计的过程中,JavaBean不是独立的。为了能够更好地封装事务逻辑、数据库操作而便于实现业务逻辑和前台程序的分离,操作的过程往往是先开发需要的JavaBean,再在适当的时候进行调用。但一个完整有效的JavaBean必然会包含一个属性,伴随若干个get/set(只读/只写)函数的变量来设计和运行的。JavaBean作为一个特殊的类,具有自己独有的特性。应该注意以下3个方面。
转载于:http://blog.csdn.net/zdwzzu2006/article/details/5151788/
]]>spring.jar
包含有完整发布模块的单个jar 包。但是不包括mock.jar, aspects.jar, spring-portlet.jar, and spring-hibernate2.jar。
spring-src.zip就是所有的源代码压缩包。
除了spring.jar 文件,Spring 还包括有其它21 个独立的jar 包,各自包含着对应的Spring组件,用户可以根据自己的需要来选择组合自己的jar 包,而不必引入整个spring.jar 的所有类文件。
这个jar 文件包含Spring 框架基本的核心工具类。Spring 其它组件要都要使用到这个包里的类,是其它组件的基本核心,当然你也可以在自己的应用系统中使用这些工具类。
外部依赖Commons Logging, (Log4J)。
这个jar 文件是所有应用都要用到的,它包含访问配置文件、创建和管理bean 以及进行Inversion of Control / Dependency Injection(IoC/DI)操作相关的所有类。如果应用只需基本的IoC/DI 支持,引入spring-core.jar 及spring-beans.jar 文件就可以了。
外部依赖spring-core,(CGLIB)。
这个jar 文件包含在应用中使用Spring 的AOP 特性时所需的类和源码级元数据支持。使用基于AOP 的Spring特性,如声明型事务管理(Declarative Transaction Management),也要在应用里包含这个jar包。
外部依赖spring-core, (spring-beans,AOP Alliance, CGLIB,Commons Attributes)。
这个jar 文件为Spring 核心提供了大量扩展。可以找到使用Spring ApplicationContext特性时所需的全部类,JDNI 所需的全部类,instrumentation组件以及校验Validation 方面的相关类。
外部依赖spring-beans, (spring-aop)。
这个jar 文件包含Spring DAO、Spring Transaction 进行数据访问的所有类。为了使用声明型事务支持,还需在自己的应用里包含spring-aop.jar。
外部依赖spring-core,(spring-aop, spring-context, JTA API)。
这个jar 文件包含对Spring 对JDBC 数据访问进行封装的所有类。
外部依赖spring-beans,spring-dao。
这个jar 文件包含支持UI模版(Velocity,FreeMarker,JasperReports),邮件服务,脚本服务(JRuby),缓存Cache(EHCache),任务计划Scheduling(uartz)方面的类。
外部依赖spring-context, (spring-jdbc, Velocity, FreeMarker, JasperReports, BSH, Groovy, JRuby, Quartz, EHCache)
这个jar 文件包含Web 应用开发时,用到Spring 框架时所需的核心类,包括自动载入Web Application Context 特性的类、Struts 与JSF 集成类、文件上传的支持类、Filter 类和大量工具辅助类。
外部依赖spring-context, Servlet API, (JSP API, JSTL, Commons FileUpload, COS)。
这个jar 文件包含Spring MVC 框架相关的所有类。包括框架的Servlets,Web MVC框架,控制器和视图支持。当然,如果你的应用使用了独立的MVC 框架,则无需这个JAR 文件里的任何类。
外部依赖spring-web, (spring-support,Tiles,iText,POI)。
spring自己实现的一个类似Spring MVC的框架。包括一个MVC框架和控制器。
外部依赖spring-web, Portlet API,(spring-webmvc)。
Struts框架支持,可以更方便更容易的集成Struts框架。
外部依赖spring-web,Struts。
这个jar 文件包含支持EJB、远程调用Remoting(RMI、Hessian、Burlap、Http Invoker、JAX-RPC)方面的类。
外部依赖spring-aop, (spring-context,spring-web,Hessian,Burlap,JAX-RPC,EJB API)。
这个jar包提供了对JMX 1.0/1.2的支持类。
外部依赖spring-beans,spring-aop, JMX API。
这个jar包提供了对JMS 1.0.2/1.1的支持类。
外部依赖spring-beans,spring-dao,JMS API。
对JCA 1.0的支持。
外部依赖spring-beans,spring-dao, JCA API。
对JDO 1.0/2.0的支持。
外部依赖spring-jdbc, JDO API, (spring-web)。
对JPA 1.0的支持。
外部依赖spring-jdbc, JPA API, (spring-web)。
对Hibernate 2.1的支持,已经不建议使用。
外部依赖spring-jdbc,Hibernate2,(spring-web)。
对Hibernate 3.0/3.1/3.2的支持。
外部依赖spring-jdbc,Hibernate3,(spring-web)。
对TopLink框架的支持。
外部依赖spring-jdbc,TopLink。
对iBATIS SQL Maps的支持。
外部依赖spring-jdbc,iBATIS SQL Maps。
另外的两个包。
这个jar 文件包含Spring 一整套mock 类来辅助应用的测试。Spring 测试套件使用了其中大量mock 类,这样测试就更加简单。模拟HttpServletRequest 和HttpServletResponse 类在Web 应用单元测试是很方便的。并且提供了对JUnit的支持。
外部依赖spring-core。
提供对AspectJ的支持,以便可以方便的将面向方面的功能集成进IDE中,比如Eclipse AJDT。
外部依赖。
WEAVER JARS (dist/weavers)说明。
Spring的InstrumentationSavingAgent (为InstrumentationLoadTimeWeaver),一个设备代理包,可以参考JDK1.5的Instrumentation功能获得更多信息。
外部依赖none (for use at JVM startup: “-javaagent:spring-agent.jar”)。
扩展Tomcat的ClassLoader,使其可以使用instrumentation(设备)类。
外部依赖none (for deployment into Tomcat’s “server/lib” directory)。
|
|
zxing-3.21.jar是根据github上面项目ZXing Project自己生成的jar包,该包主要应用于二维码生成,下面讲述怎么把怎么把zxing-3.21.jar添加到本地的maven仓库中。
需要配置JDK和maven环境,完成后以管理员身份打开命令提示符窗口(cmd),再输入下面相关的语法。
|
|
注意:地址+jar包名,即C:\Users\eric\Desktop\zxing\3.21\zxing-3.21.jar要加引号””,”参数二\参数三\参数四”这也是jar包在仓库中的地址。
查看添加的zxing-3.21.jar的dependency
|
|
|
|
ZADD key score member [[score member] [score member] …]
将一个或多个 member 元素及其 score 值加入到有序集 key 当中。
如果某个 member 已经是有序集的成员,那么更新这个 member 的 score 值,并通过重新插入这个 member 元素,来保证该 member 在正确的位置上。
score 值可以是整数值或双精度浮点数。
如果 key 不存在,则创建一个空的有序集并执行 ZADD 操作。
当 key 存在但不是有序集类型时,返回一个错误。
在 Redis 2.4 版本以前, ZADD 每次只能添加一个元素。
版本>= 1.2.0
O(M*log(N)), N 是有序集的基数, M 为成功添加的新成员的数量。
被成功添加的新成员的数量,不包括那些被更新的、已经存在的成员。
|
|
ZCARD key
返回有序集 key 的基数。
版本>= 1.2.0
O(1)
当 key 存在且是有序集类型时,返回有序集的基数。
当 key 不存在时,返回 0 。
|
|
ZCOUNT key min max
返回有序集 key 中, score 值在 min 和 max 之间(默认包括 score 值等于 min 或 max )的成员的数量。
版本>= 2.0.0
O(log(N)+M), N 为有序集的基数, M 为值在 min 和 max 之间的元素的数量。
score 值在 min 和 max 之间的成员的数量。
|
|
ZINCRBY key increment member
为有序集 key 的成员 member 的 score 值加上增量 increment 。
可以通过传递一个负数值 increment ,让 score 减去相应的值,比如 ZINCRBY key -5 member ,就是让 member 的 score 值减去 5 。
当 key 不存在,或 member 不是 key 的成员时, ZINCRBY key increment member 等同于 ZADD key increment member 。
当 key 不是有序集类型时,返回一个错误。
score 值可以是整数值或双精度浮点数。
版本>= 1.2.0
O(log(N))
member 成员的新 score 值,以字符串形式表示。
|
|
ZRANGE key start stop [WITHSCORES]
返回有序集 key 中,指定区间内的成员。
其中成员的位置按 score 值递增(从小到大)来排序。
具有相同 score 值的成员按字典序(lexicographical order )来排列。
如果你需要成员按 score 值递减(从大到小)来排列,请使用 ZREVRANGE 命令。
下标参数 start 和 stop 都以 0 为底,也就是说,以 0 表示有序集第一个成员,以 1 表示有序集第二个成员,以此类推。
你也可以使用负数下标,以 -1 表示最后一个成员, -2 表示倒数第二个成员,以此类推。
超出范围的下标并不会引起错误。
可以通过使用 WITHSCORES 选项,来让成员和它的 score 值一并返回,返回列表以 value1,score1, …, valueN,scoreN 的格式表示。
客户端库可能会返回一些更复杂的数据类型,比如数组、元组等。
版本>= 1.2.0
O(log(N)+M), N 为有序集的基数,而 M 为结果集的基数。
指定区间内,带有 score 值(可选)的有序集成员的列表。
|
|
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。
具有相同 score 值的成员按字典序(lexicographical order)来排列(该属性是有序集提供的,不需要额外的计算)。
可选的 LIMIT 参数指定返回结果的数量及区间(就像SQL中的 SELECT LIMIT offset, count ),注意当 offset 很大时,定位 offset 的操作可能需要遍历整个有序集,此过程最坏复杂度为 O(N) 时间。
可选的 WITHSCORES 参数决定结果集是单单返回有序集的成员,还是将有序集成员及其 score 值一起返回。
该选项自 Redis 2.0 版本起可用。
区间及无限
min 和 max 可以是 -inf 和 +inf ,这样一来,你就可以在不知道有序集的最低和最高 score 值的情况下,使用 ZRANGEBYSCORE 这类命令。
默认情况下,区间的取值使用闭区间 (小于等于或大于等于),你也可以通过给参数前增加 ( 符号来使用可选的开区间 (小于或大于)。
举个例子
ZRANGEBYSCORE zset (1 5 返回所有符合条件 1 < score <= 5 的成员,而
ZRANGEBYSCORE zset (5 (10 则返回所有符合条件 5 < score < 10 的成员。
版本>= 1.0.5
O(log(N)+M), N 为有序集的基数, M 为被结果集的基数。
指定区间内,带有 score 值(可选)的有序集成员的列表。
|
|
ZRANK key member
返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递增(从小到大)顺序排列。
排名以 0 为底,也就是说, score 值最小的成员排名为 0 。
使用 ZREVRANK 命令可以获得成员按 score 值递减(从大到小)排列的排名。
版本>= 2.0.0
O(log(N))
如果 member 是有序集 key 的成员,返回 member 的排名。
如果 member 不是有序集 key 的成员,返回 nil 。
|
|
ZREM key member [member …]
移除有序集 key 中的一个或多个成员,不存在的成员将被忽略。
当 key 存在但不是有序集类型时,返回一个错误。
在 Redis 2.4 版本以前, ZREM 每次只能删除一个元素。
= 1.2.0
时间复杂度
O(M*log(N)), N 为有序集的基数, M 为被成功移除的成员的数量。
返回值
被成功移除的成员的数量,不包括被忽略的成员。
示例
123456789101112131415161718192021222324 # 测试数据redis> ZRANGE page_rank 0 -1 WITHSCORES1) "bing.com"2) "8"3) "baidu.com"4) "9"5) "google.com"6) "10"# 移除单个元素redis> ZREM page_rank google.com(integer) 1redis> ZRANGE page_rank 0 -1 WITHSCORES1) "bing.com"2) "8"3) "baidu.com"4) "9"# 移除多个元素redis> ZREM page_rank baidu.com bing.com(integer) 2redis> ZRANGE page_rank 0 -1 WITHSCORES(empty list or set)# 移除不存在元素redis> ZREM page_rank non-exists-element(integer) 0
ZREMRANGEBYRANK key start stop
移除有序集 key 中,指定排名(rank)区间内的所有成员。
区间分别以下标参数 start 和 stop 指出,包含 start 和 stop 在内。
下标参数 start 和 stop 都以 0 为底,也就是说,以 0 表示有序集第一个成员,以 1 表示有序集第二个成员,以此类推。
你也可以使用负数下标,以 -1 表示最后一个成员, -2 表示倒数第二个成员,以此类推。
版本>= 2.0.0
O(log(N)+M), N 为有序集的基数,而 M 为被移除成员的数量。
被移除成员的数量。
|
|
ZREMRANGEBYSCORE key min max
移除有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。
自版本2.1.6开始, score 值等于 min 或 max 的成员也可以不包括在内,详情请参见 ZRANGEBYSCORE 命令。
版本>= 1.2.0
O(log(N)+M), N 为有序集的基数,而 M 为被移除成员的数量。
被移除成员的数量。
|
|
ZREVRANGE key start stop [WITHSCORES]
返回有序集 key 中,指定区间内的成员。
其中成员的位置按 score 值递减(从大到小)来排列。
具有相同 score 值的成员按字典序的逆序(reverse lexicographical order)排列。
除了成员按 score 值递减的次序排列这一点外, ZREVRANGE 命令的其他方面和 ZRANGE 命令一样。
版本>= 1.2.0
O(log(N)+M), N 为有序集的基数,而 M 为结果集的基数。
指定区间内,带有 score 值(可选)的有序集成员的列表。
|
|
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
返回有序集 key 中, score 值介于 max 和 min 之间(默认包括等于 max 或 min )的所有的成员。有序集成员按 score 值递减(从大到小)的次序排列。
具有相同 score 值的成员按字典序的逆序(reverse lexicographical order )排列。
除了成员按 score 值递减的次序排列这一点外, ZREVRANGEBYSCORE 命令的其他方面和 ZRANGEBYSCORE 命令一样。
版本>= 2.2.0
O(log(N)+M), N 为有序集的基数, M 为结果集的基数。
指定区间内,带有 score 值(可选)的有序集成员的列表。
|
|
ZREVRANK key member
返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递减(从大到小)排序。
排名以 0 为底,也就是说, score 值最大的成员排名为 0 。
使用 ZRANK 命令可以获得成员按 score 值递增(从小到大)排列的排名。
版本>= 2.0.0
O(log(N))
如果 member 是有序集 key 的成员,返回 member 的排名。
如果 member 不是有序集 key 的成员,返回 nil 。
|
|
ZSCORE key member
返回有序集 key 中,成员 member 的 score 值。
如果 member 元素不是有序集 key 的成员,或 key 不存在,返回 nil 。
版本>= 1.2.0
O(1)
member 成员的 score 值,以字符串形式表示。
|
|
ZUNIONSTORE destination numkeys key [key …] [WEIGHTS weight [weight …]] [AGGREGATE SUM|MIN|MAX]
计算给定的一个或多个有序集的并集,其中给定 key 的数量必须以 numkeys 参数指定,并将该并集(结果集)储存到 destination 。
默认情况下,结果集中某个成员的 score 值是所有给定集下该成员 score 值之 和 。
WEIGHTS
使用 WEIGHTS 选项,你可以为 每个 给定有序集 分别 指定一个乘法因子(multiplication factor),每个给定有序集的所有成员的 score 值在传递给聚合函数(aggregation function)之前都要先乘以该有序集的因子。
如果没有指定 WEIGHTS 选项,乘法因子默认设置为 1 。
AGGREGATE
使用 AGGREGATE 选项,你可以指定并集的结果集的聚合方式。
默认使用的参数 SUM ,可以将所有集合中某个成员的 score 值之 和 作为结果集中该成员的 score 值;使用参数 MIN ,可以将所有集合中某个成员的 最小 score 值作为结果集中该成员的 score 值;而参数 MAX 则是将所有集合中某个成员的 最大 score 值作为结果集中该成员的 score 值。
版本>= 2.0.0
O(N)+O(M log(M)), N 为给定有序集基数的总和, M 为结果集的基数。
保存到 destination 的结果集的基数。
|
|
ZINTERSTORE destination numkeys key [key …] [WEIGHTS weight [weight …]] [AGGREGATE SUM|MIN|MAX]
计算给定的一个或多个有序集的交集,其中给定 key 的数量必须以 numkeys 参数指定,并将该交集(结果集)储存到 destination 。
默认情况下,结果集中某个成员的 score 值是所有给定集下该成员 score 值之和.
关于 WEIGHTS 和 AGGREGATE 选项的描述,参见 ZUNIONSTORE 命令。
版本>= 2.0.0
O(NK)+O(Mlog(M)), N 为给定 key 中基数最小的有序集, K 为给定有序集的数量, M 为结果集的基数。
保存到 destination 的结果集的基数。
|
|
ZSCAN key cursor [MATCH pattern] [COUNT count]
详细信息请参考 SCAN 命令。
]]>SET key value [EX seconds] [PX milliseconds] [NX|XX]
将字符串值 value 关联到 key 。
如果 key 已经持有其他值, SET 就覆写旧值,无视类型。
对于某个原本带有生存时间(TTL)的键来说, 当 SET 命令成功在这个键上执行时, 这个键原有的 TTL 将被清除。
EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。
NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
XX :只在键已经存在时,才对键进行设置操作。
SET 在设置操作成功完成时,才返回 OK 。如果设置了 NX 或者 XX ,但因为条件没达到而造成设置操作未执行,那么命令返回空批量回复(NULL Bulk Reply)。
|
|
SETBIT key offset value
对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。
位的设置或清除取决于 value 参数,可以是 0 也可以是 1 。
当 key 不存在时,自动生成一个新的字符串值。
字符串会进行伸展(grown)以确保它可以将 value 保存在指定的偏移量上。当字符串值进行伸展时,空白位置以 0 填充。
offset 参数必须大于或等于 0 ,小于 2^32 (bit 映射被限制在 512 MB 之内)。
指定偏移量原来储存的位。
|
|
SETEX key seconds value
将值 value 关联到 key ,并将 key 的生存时间设为 seconds (以秒为单位)。
如果 key 已经存在, SETEX 命令将覆写旧值。
设置成功时返回 OK 。当 seconds 参数不合法时,返回一个错误。
|
|
SETNX key value
将 key 的值设为 value ,当且仅当 key 不存在。
若给定的 key 已经存在,则 SETNX 不做任何动作。
SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
设置成功,返回 1 。设置失败,返回 0 。
|
|
SETRANGE key offset value
用 value 参数覆写(overwrite)给定 key 所储存的字符串值,从偏移量 offset 开始。
不存在的 key 当作空白字符串处理。
SETRANGE 命令会确保字符串足够长以便将 value 设置在指定的偏移量上,如果给定 key 原来储存的字符串长度比偏移量小(比如字符串只有 5 个字符长,但你设置的 offset 是 10 ),那么原字符和偏移量之间的空白将用零字节(zerobytes, “\x00” )来填充。
注意你能使用的最大偏移量是 2^29-1(536870911) ,因为 Redis 字符串的大小被限制在 512 兆(megabytes)以内。如果你需要使用比这更大的空间,你可以使用多个 key 。
注意:当生成一个很长的字符串时,Redis 需要分配内存空间,该操作有时候可能会造成服务器阻塞(block)。在2010年的Macbook Pro上,设置偏移量为 536870911(512MB 内存分配),耗费约 300 毫秒, 设置偏移量为 134217728(128MB 内存分配),耗费约 80 毫秒,设置偏移量 33554432(32MB 内存分配),耗费约 30 毫秒,设置偏移量为 8388608(8MB 内存分配),耗费约 8 毫秒。 注意若首次内存分配成功之后,再对同一个 key 调用 SETRANGE 操作,无须再重新内存。
被 SETRANGE 修改之后,字符串的长度。
|
|
GET key
返回 key 所关联的字符串值。
如果 key 不存在那么返回特殊值 nil 。
假如 key 储存的值不是字符串类型,返回一个错误,因为 GET 只能用于处理字符串值。
当 key 不存在时,返回 nil ,否则,返回 key 的值。如果 key 不是字符串类型,那么返回一个错误。
|
|
GETBIT key offset
对 key 所储存的字符串值,获取指定偏移量上的位(bit)。
当 offset 比字符串值的长度大,或者 key 不存在时,返回 0 。
字符串值指定偏移量上的位(bit)。对不存在的 key 或者不存在的 offset 进行 GETBIT, 返回 0。
|
|
GETRANGE key start end
返回 key 中字符串值的子字符串,字符串的截取范围由 start 和 end 两个偏移量决定(包括 start 和 end 在内)。
负数偏移量表示从字符串最后开始计数, -1 表示最后一个字符, -2 表示倒数第二个,以此类推。
GETRANGE 通过保证子字符串的值域(range)不超过实际字符串的值域来处理超出范围的值域请求。
截取得出的子字符串。
|
|
GETSET key value
将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
当 key 存在但不是字符串类型时,返回一个错误。
版本>= 1.0.0
O(1)
返回给定 key 的旧值。
当 key 没有旧值时,也即是, key 不存在时,返回 nil 。
|
|
APPEND key value
如果 key 已经存在并且是一个字符串, APPEND 命令将 value 追加到 key 原来的值的末尾。
如果 key 不存在, APPEND 就简单地将给定 key 设为 value ,就像执行 SET key value 一样。
= 2.0.0
时间复杂度
平摊O(1)
返回值
追加 value 之后, key 中字符串的长度。
|
|
BITCOUNT key [start] [end]
计算给定字符串中,被设置为 1 的比特位的数量。
一般情况下,给定的整个字符串都会被进行计数,通过指定额外的 start 或 end 参数,可以让计数只在特定的位上进行。
start 和 end 参数的设置和 GETRANGE 命令类似,都可以使用负数值:比如 -1 表示最后一个位,而 -2 表示倒数第二个位,以此类推。
不存在的 key 被当成是空字符串来处理,因此对一个不存在的 key 进行 BITCOUNT 操作,结果为 0 。
版本>= 2.6.0
O(N)
被设置为 1 的位的数量。
|
|
BITOP operation destkey key [key …]
对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
operation 可以是 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种:
除了 NOT 操作之外,其他操作都可以接受一个或多个 key 作为输入。
当 BITOP 处理不同长度的字符串时,较短的那个字符串所缺少的部分会被看作 0 。空的 key 也被看作是包含 0 的字符串序列。
版本>= 2.6.0
O(N)
保存到 destkey 的字符串的长度,和输入 key 中最长的字符串长度相等。
注意: BITOP 的复杂度为 O(N) ,当处理大型矩阵(matrix)或者进行大数据量的统计时,最好将任务指派到附属节点(slave)进行,避免阻塞主节点。
|
|
DECR key
将 key 中储存的数字值减一。
如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 DECR 操作。
如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
本操作的值限制在 64 位(bit)有符号数字表示之内。
版本>= 1.0.0
O(1)
执行 DECR 命令之后 key 的值。
|
|
DECRBY key decrement
将 key 所储存的值减去减量 decrement 。
如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 DECRBY 操作。
如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
本操作的值限制在 64 位(bit)有符号数字表示之内。
版本>= 1.0.0
O(1)
减去 decrement 之后, key 的值。
|
|
INCR key
将 key 中储存的数字值增一。
如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。
如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
本操作的值限制在 64 位(bit)有符号数字表示之内。
注意:这是一个针对字符串的操作,因为 Redis 没有专用的整数类型,所以 key 内储存的字符串被解释为十进制 64 位有符号整数来执行 INCR 操作。
版本>= 1.0.0
O(1)
执行 INCR 命令之后 key 的值。
|
|
INCRBY key increment
将 key 所储存的值加上增量 increment 。
如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCRBY 命令。
如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
本操作的值限制在 64 位(bit)有符号数字表示之内。
版本>= 1.0.0
O(1)
加上 increment 之后, key 的值。
|
|
INCRBYFLOAT key increment
为 key 中所储存的值加上浮点数增量 increment 。
如果 key 不存在,那么 INCRBYFLOAT 会先将 key 的值设为 0 ,再执行加法操作。
如果命令执行成功,那么 key 的值会被更新为(执行加法之后的)新值,并且新值会以字符串的形式返回给调用者。
无论是 key 的值,还是增量 increment ,都可以使用像 2.0e7 、 3e5 、 90e-2 那样的指数符号(exponential notation)来表示,但是,执行 INCRBYFLOAT 命令之后的值总是以同样的形式储存,也即是,它们总是由一个数字,一个(可选的)小数点和一个任意位的小数部分组成(比如 3.14 、 69.768 ,诸如此类),小数部分尾随的 0 会被移除,如果有需要的话,还会将浮点数改为整数(比如 3.0 会被保存成 3 )。
除此之外,无论加法计算所得的浮点数的实际精度有多长, INCRBYFLOAT 的计算结果也最多只能表示小数点的后十七位。
当以下任意一个条件发生时,返回一个错误:
版本>= 2.6.0
O(1)
执行命令之后 key 的值。
|
|
MGET key [key …]
返回所有(一个或多个)给定 key 的值。
如果给定的 key 里面,有某个 key 不存在,那么这个 key 返回特殊值 nil 。因此,该命令永不失败。
版本>= 1.0.0
O(N) , N 为给定 key 的数量。
一个包含所有给定 key 的值的列表。
|
|
MSET key value [key value …]
同时设置一个或多个 key-value 对。
如果某个给定 key 已经存在,那么 MSET 会用新值覆盖原来的旧值,如果这不是你所希望的效果,请考虑使用 MSETNX 命令:它只会在所有给定 key 都不存在的情况下进行设置操作。
MSET 是一个原子性(atomic)操作,所有给定 key 都会在同一时间内被设置,某些给定 key 被更新而另一些给定 key 没有改变的情况,不可能发生。
版本>= 1.0.1
O(N), N 为要设置的 key 数量。
总是返回 OK (因为 MSET 不可能失败)
|
|
MSETNX key value [key value …]
同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。
即使只有一个给定 key 已存在, MSETNX 也会拒绝执行所有给定 key 的设置操作。
MSETNX 是原子性的,因此它可以用作设置多个不同 key 表示不同字段(field)的唯一性逻辑对象(unique logic object),所有字段要么全被设置,要么全不被设置。
版本>= 1.0.1
O(N), N 为要设置的 key 的数量。
当所有 key 都成功设置,返回 1 。
如果所有给定 key 都设置失败(至少有一个 key 已经存在),那么返回 0 。
|
|
PSETEX key milliseconds value
这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。
版本>= 2.6.0
O(1)
设置成功时返回 OK 。
|
|
STRLEN key
返回 key 所储存的字符串值的长度。
当 key 储存的不是字符串值时,返回一个错误。
版本>= 2.2.0
O(1)
字符串值的长度。
当 key 不存在时,返回 0 。
|
|