Netty

Netty version latest 4.1.68 current 4.1.22 Netty是Java领域有名的开源网络库,特点是高性能和高扩展性,因此很多流行的框架都是基于它来构建的,比如我们熟知的Dubbo、Rocketmq、Hadoop等 Netty是由JBOSS提供的一个java开源框架. Netty提供异步的, 事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序. 也就是说,Netty 是一个基于NIO的客户,服务器端编程框架, 使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户,服务端应用。Netty相当简化和流线化了网络应用的编程开发过程,例如,TCP和UDP的socket服务开发。 “快速"和"简单"并不意味着会让你的最终应用产生维护性或性能上的问题。Netty 是一个吸收了多种协议的实现经验,这些协议包括FTP,SMTP,HTTP,各种二进制,文本协议,并经过相当精心设计的项目,最终,Netty 成功的找到了一种方式,在保证易于开发的同时还保证了其应用的性能,稳定性和伸缩性。 netty uml @startuml interface ChannelHandlerContext interface ResourceLeakHint class NioEventLoop{ protected void run() private void processSelectedKeys() private void processSelectedKeysOptimized() private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) } class AbstractNioByteChannel class NioByteUnsafe{ public final void read() } class DefaultChannelPipeline{ AbstractChannelHandlerContext head public final ChannelPipeline fireChannelRead(Object msg) } class HeadContext class AbstractChannelHandlerContext { AbstractChannelHandlerContext next AbstractChannelHandlerContext prev DefaultChannelPipeline pipeline ChannelHandlerContext fireChannelRead(final Object msg) AbstractChannelHandlerContext findContextInbound() void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) void invokeChannelRead(Object msg) boolean invokeHandler() } DefaultAttributeMap<|--AbstractChannelHandlerContext ChannelHandlerContext<|--AbstractChannelHandlerContext ResourceLeakHint<|--AbstractChannelHandlerContext DefaultChannelPipeline <|- AbstractChannelHandlerContext HeadContext <|- DefaultChannelPipeline class SimpleChannelInboundHandler { public void channelRead(ChannelHandlerContext ctx, Object msg) public void channelActive(ChannelHandlerContext ctx) } ChannelInboundHandlerAdapter<|--SimpleChannelInboundHandler class ServerBootstrap class AbstractBootstrap interface Cloneable Cloneable<|--AbstractBootstrap AbstractBootstrap<|--ServerBootstrap @enduml 时序图 @startuml SingleThreadEventExecutor -> NioEventLoop: run() NioEventLoop -> NioByteUnsafe: read() AbstractChannelHandlerContext -> AbstractChannelHandlerContext: ChannelHandlerContext fireChannelRead(final Object msg) AbstractChannelHandlerContext -> AbstractChannelHandlerContext: AbstractChannelHandlerContext findContextInbound(int mask) AbstractChannelHandlerContext -> AbstractChannelHandlerContext: invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) AbstractChannelHandlerContext -> ChannelHandler: invokeChannelRead(Object msg) ChannelHandler --> Handler0: channelRead @enduml https://netty.io/4.1/api/io/netty/channel/ChannelPipeline.html ...

2013-11-17 · 2 min · 273 words · -

Java 强引用, 软引用, 弱引用, 虚引用

Java 强引用, 软引用, 弱引用, 虚引用 强引用 StrongReference 强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。 Object o=new Object(); // 强引用 如果不使用时,要通过如下方式来弱化引用,如下: o=null; // 帮助垃圾收集器回收此对象 显式地设置o为null,或超出对象的生命周期范围,则gc认为该对象不存在引用,这时就可以回收这个对象。具体什么时候收集这要取决于gc的算法。 在一个方法的内部有一个强引用,这个引用保存在栈中,而真正的引用内容 (Object) 保存在堆中。当这个方法运行完成后就会退出方法栈,则引用内容的引用不存在,这个Object会被回收。 但是如果这个o是全局的变量时,就需要在不用这个对象时赋值为null,因为强引用不会被垃圾回收。 强引用在实际中有非常重要的用处,举个ArrayList的实现源代码: private transient Object[] elementData; public void clear() { modCount++; // Let gc do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; } 在ArrayList类中定义了一个私有的变量elementData数组,在调用方法清空数组时可以看到为每个数组内容赋值为null。不同于elementData=null,强引用仍然存在,避免在后续调用 add()等方法添加元素时进行重新的内存分配。使用如clear()方法中释放内存的方法对数组中存放的引用类型特别适用,这样就可以及时释放内存。 软引用 SoftReference 如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 它兼有了StrongReference和WeakReference的好处,既能停留在内存中,又能在内存不足时去处理。 String str=new String(“abc”); // 强引用 SoftReference softRef=new SoftReference(str); // 软引用 ...

2013-11-17 · 1 min · 121 words · -

JRE和JDK的区别

JRE和JDK的区别 JDK JDK 是整个Java的核心,包括了Java运行环境(JRE) (Java Runtime Envirnment), 一堆Java工具和Java基础的类库 (rt.jar) 。不论什么Java应用服务器实质都是内置了某个版本的JDK.最主流的JDK是Sun公司发布的JDK,除了Sun之外,还有很多公司和组织都开发了自己的JDK,例如IBM公司开发的JDK,BEA公司的Jrocket,还有GNU组织开发的JDK等等。其中IBM的JDK包含的JVM (Java Virtual Machine) 运行效率要比Sun JDK包含的JVM高出许多。而专门运行在x86平台的Jrocket在服务端运行效率也要比Sun JDK好很多。但不管怎么说,我们还是需要先把Sun JDK掌握好。 JDK一般有三种版本: SE (J2SE) ,standard edition,标准版,是我们通常用的一个版本EE (J2EE) ,enterpsise edtion,企业版,使用这种JDK开发J2EE应用程序,ME (J2ME) ,micro edtion,主要用于移动设备、嵌入式设备上的java应用程序Java开发工具 (JDK) 是许多Java专家最初使用的开发环境。尽管许多编程人员已经使用第三方的开发工具,但JDK仍被当作Java开发的重要工具。JDK由一个标准类库和一组建立,测试及建立文档的Java实用程序组成。其核心Java API是一些预定义的类库,开发人员需要用这些类来访问Java语言的功能。Java API包括一些重要的语言结构以及基本图形,网络和文件I/O.一般来说,Java API的非I/O部分对于运行Java的所有平台是相同的,而I/O部分则仅在通用Java环境中实现。 作为JDK实用程序, 工具库中有七种主要程序。 Javac, Java, appletviewer, Javadoc, Jdb, Javah, Javap; [[jdk-tool#jdk tool java tool jvm tool]] JRE JRE (Java Runtime Environment,Java运行环境) ,运行JAVA程序所必须的环境的集合,包含JVM标准实现及Java核心类库。是Sun的产品,包括两部分: JavaRuntimeEnvironment和JavaPlug-inJavaRuntimeEnvironment (JRE) 是可以在其上运行、测试和传输应用程序的Java平台。它包括Java虚拟机、Java平台核心类和支持文件。它不包含开发工具——编译器、调试器和其它工具。JRE需要辅助软件 ——JavaPlug-in——以便在浏览器中运行applet.J2RE是Java2 Runtime Environment,即Java运行环境,有时简称JRE.如果你只需要运行Java程序或Applet,下载并安装它即可。如果你要自行开发 Java软件,请下载JDK.在JDK中附带有JRE.注意由于Microsoft对Java的支持不完全,请不要使用IE自带的虚拟机来运行 Applet,务必安装一个JRE或JDK.

2013-10-20 · 1 min · 62 words · -

虚拟机栈(Virtual Machine Stack),栈帧(Stack Frame)

虚拟机栈(Virtual Machine Stack),栈帧(Stack Frame) 栈帧(Stack Frame) 栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区的虚拟机栈 (Virtual Machine Stack)的栈元素。栈帧存储了方法的局部变量表,操作数栈,动态连接和方法返回地址等信息。第一个方法从调用开始到执行完成,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。 每一个栈帧都包括了局部变量表,操作数栈,动态连接,方法返回地址和一些额外的附加信息。在编译代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到了方法表的Code属性中,因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体虚拟机的实现。 一个线程中的方法调用链可能会很长,很多方法都同时处理执行状态。对于执行引擎来讲,活动线程中,只有虚拟机栈顶的栈帧才是有效的,称为当前栈帧(Current Stack Frame),这个栈帧所关联的方法称为当前方法(Current Method)。执行引用所运行的所有字节码指令都只针对当前栈帧进行操作。 局部变量表 局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在Java程序编译为Class文件时,就在方法表的Code属性的max_locals数据项中确定了该方法需要分配的最大局部变量表的容量。 在方法执行时,虚拟机是使用局部变量表完成参数变量列表的传递过程,如果是实例方法,那么局部变量表中的每0位索引的Slot默认是用于传递方法所属对象实例的引用,在方法中可以通过关键字"this"来访问这个隐含的参数,其余参数则按照参数列表的顺序来排列,占用从1开始的局部变量Slot,参数表分配完毕后,再根据方法体内部定义的变量顺序和作用域来分配其余的Slot。局部变量表中的Slot是可重用的,方法体中定义的变量,其作用域并不一定会覆盖整个方法,如果当前字节码PC计算器的值已经超出了某个变量的作用域,那么这个变量对应的Slot就可以交给其它变量使用。 局部变量不像前面介绍的类变量那样存在"准备阶段"。类变量有两次赋初始值的过程,一次在准备阶段,赋予系统初始值;另外一次在初始化阶段,赋予程序员定义的值。因此即使在初始化阶段程序员没有为类变量赋值也没有关系,类变量仍然具有一个确定的初始值。但局部变量就不一样了,如果一个局部变量定义了但没有赋初始值是不能使用的。 操作数栈 Java虚拟机的解释执行引擎被称为"基于栈的执行引擎",其中所指的栈就是指-操作数栈。 和局部变量区一样,操作数栈也是被组织成一个以字长为单位的数组。但是和前者不同的是,它不是通过索引来访问,而是通过标准的栈操作—压栈和出栈—来访问的。比如,如果某个指令把一个值压入到操作数栈中,稍后另一个指令就可以弹出这个值来使用。 虚拟机在操作数栈中存储数据的方式和在局部变量区中是一样的: 如int、long、float、double、reference和returnType的存储。对于byte、short以及char类型的值在压入到操作数栈之前,也会被转换为int。 虚拟机把操作数栈作为它的工作区——大多数指令都要从这里弹出数据,执行运算,然后把结果压回操作数栈。比如,iadd 指令就要从操作数栈中弹出两个整数,执行加法运算,其结果又压回到操作数栈中,看看下面的示例,它演示了虚拟机是如何把两个int类型的局部变量相加,再把结果保存到第三个局部变量的: begin iload_0 // push the int in local variable 0 onto the stack iload_1 // push the int in local variable 1 onto the stack iadd // pop two ints, add them, push result istore_2 // pop int, store into local variable 2 end 在这个字节码序列里,前两个指令 iload_0和iload_1将存储在局部变量中索引为0和1的整数压入操作数栈中,其后iadd指令从操作数栈中弹出那两个整数相加,再将结果压入操作数栈。第四条指令istore_2则从操作数栈中弹出结果,并把它存储到局部变量区索引为2的位置。 操作数栈也常被称为操作栈,它是一个后入先出栈(Last in First out,LIFO)。同局部变量表一样,操作数栈的最大深度也是编译的时候被写入到方法表的Code属性的max_stacks数据项中。操作数栈的每一个元素可以是任意Java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。栈容量的单位为"字宽",对于32位虚拟机来说,一个"字宽"占4个字节,对于64位虚拟机来说,一个"字宽"占8个字节。 ...

2013-09-16 · 1 min · 98 words · -

偏向锁(Biased Locking)

偏向锁(Biased Locking) Java偏向锁(Biased Locking) 是Java6引入的一项多线程优化。它通过消除资源无竞争情况下的同步原语,进一步提高了程序的运行性能。 轻量级锁也是一种多线程优化,它与偏向锁的区别在于,轻量级锁是通过CAS来避免进入开销较大的互斥操作,而偏向锁是在无竞争场景下完全消除同步,连CAS也不执行 (CAS本身仍旧是一种操作系统同步原语,始终要在JVM与OS之间来回,有一定的开销) 。 所谓的无竞争场景,举个例子,就是单线程访问带同步的资源或方法。 偏向锁实现原理 偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在接下来的运行过程中,该锁没有被其他的线程访问,则持有偏向锁的线程将永远不需要触发同步。 如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会尝试消除它身上的偏向锁,将锁恢复到标准的轻量级锁。(偏向锁只能在单线程下起作用) 偏向模式和非偏向模式,在下面的mark word表中,主要体现在thread ID字段是否为空。 挂起持有偏向锁的线程,这步操作类似GC的pause,但不同之处是,它只挂起持有偏向锁的线程 (非当前线程) 。 在抢占模式的橙色区域说明中有提到,指向当前堆栈中最近的一个lock record (在轻量级锁原理一文有讲到,lock record是进入锁前会在stack上创建的一份内存空间) 。 这里提到的最近的一个lock record,其实就是当前锁所在的stack frame上分配的lock record。 整个步骤是从偏向锁恢复到轻量级锁的过程。 偏向锁也会带来额外开销 在JDK6中,偏向锁是默认启用的。它提高了单线程访问同步资源的性能。 但试想一下,如果你的同步资源或代码一直都是多线程访问的,那么消除偏向锁这一步骤对你来说就是多余的。事实上,消除偏向锁的开销还是蛮大的。 所以在你非常熟悉自己的代码前提下,大可禁用偏向锁 -XX:-UseBiasedLocking 。 http://blog.csdn.net/hsuxu/article/details/9472381 http://www.cnblogs.com/paddix/p/5405678.html

2013-09-13 · 1 min · 36 words · -

进入safepoint时如何让Java线程全部阻塞

进入safepoint时如何让Java线程全部阻塞 http://blog.csdn.net/iter_zc/article/details/41892567 在这篇聊聊JVM (六) 理解JVM的safepoint 中说了safepoint的基本概念,VM thread在进行GC前,必须要让所有的Java线程阻塞,从而stop the world,开始标记。JVM采用了主动式阻塞的方式,Java线程不是随时都可以进入阻塞,需要运行到特定的点,叫safepoint,在这些点的位置Java线程可以被全部阻塞,整个堆的状态是一个暂时稳定的状态,OopMap指出了这个时刻,寄存器和栈内存的哪些具体的地址是引用,从而可以快速找到GC roots来进行对象的标记操作。 那么当Java线程运行到safepoint的时候,JVM如何让Java线程挂起呢?这是一个复杂的操作。很多文章里面说了JIT编译模式下,编译器会把很多safepoint检查的操作插入到编译偶的指令中,比如下面的指令来自内存篇: JVM内存回收理论与实现 0x01b6d627: call 0x01b2b210 ; OopMap{[60]=Oop off=460} ;_invokeinterface size ; - Client1::main@113 (line 23) ; {virtual_call} 0x01b6d62c: nop ; OopMap{[60]=Oop off=461} ;_if_icmplt ; - Client1::main@118 (line 23) 0x01b6d62d: test %eax,0x160100 ; {poll} 0x01b6d633: mov 0x50(%esp),%esi 0x01b6d637: cmp %eax,%esi test %eax,0x160100 就是一个safepoint polling page操作。当JVM要停止所有的Java线程时会把一个特定内存页设置为不可读,那么当Java线程读到这个位置的时候就会被挂起 这个回答虽然是没有问题,但是有些点到为止的感觉,有些意犹未尽,我又深挖了一些资料,很多资料连着一起看才能说明问题,下面再深入说说到底JVM是如何让Java线程全部 阻塞的。 Points on Safepoints 这篇文章说明了一些问题。首先是关于一些safepoint的观点 All commercial GCs use safepoints. The GC reigns in all threads at safepoints. This is when it has exact knowledge of things touched by the threads. They can also be used for non-GC activity like optimization. A thread at a safepoint is not necessarily idle but it often is. Safepoint opportunities should be frequent. All threads need to reach a global safepoint typically every dozen or so instructions (for example, at the end of loops). safepoint机制可以stop the world,不仅仅是在GC的时候用,有很多其他地方也会用它来stop the world,阻塞所有Java线程,从而可以安全地进行一些操作。 看一下OpenJDK里面关于safepoint的一些说明 ```java // Begin the process of bringing the system to a safepoint. // Java threads can be in several different states and are // stopped by different mechanisms: // // 1. Running interpreted // The interpeter dispatch table is changed to force it to // check for a safepoint condition between bytecodes. // 2. Running in native code // When returning from the native code, a Java thread must check // the safepoint _state to see if we must block. If the // VM thread sees a Java thread in native, it does // not wait for this thread to block. The order of the memory // writes and reads of both the safepoint state and the Java // threads state is critical. In order to guarantee that the // memory writes are serialized with respect to each other, // the VM thread issues a memory barrier instruction // (on MP systems). In order to avoid the overhead of issuing // a mem barrier for each Java thread making native calls, each Java // thread performs a write to a single memory page after changing // the thread state. The VM thread performs a sequence of // mprotect OS calls which forces all previous writes from all // Java threads to be serialized. This is done in the // os::serialize_thread_states() call. This has proven to be // much more efficient than executing a membar instruction // on every call to native code. // 3. Running compiled Code // Compiled code reads a global (Safepoint Polling) page that // is set to fault if we are trying to get to a safepoint. // 4. Blocked // A thread which is blocked will not be allowed to return from the // block condition until the safepoint operation is complete. // 5. In VM or Transitioning between states // If a Java thread is currently running in the VM or transitioning // between states, the safepointing code will wait for the thread to // block itself when it attempts transitions to a new state. // 可以看到JVM在阻塞全部Java线程之前,Java线程可能处在不同的状态,这篇聊聊JVM (五) 从JVM角度理解线程 说了JVM里面定义的线程所有的状态。 ...

2013-09-13 · 6 min · 1110 words · -

从JVM角度理解线程

从JVM角度理解线程 http://blog.csdn.net/iter_zc/article/details/41843595 这篇说说如何从JVM的角度来理解线程,可以对Java的线程模型有一个更加深入的理解,对GC的一些细节也会理解地更加深刻。本文基于HotSpot的OpenJDK7实现。 我们知道JVM主要是用C++实现的,JVM定义的Thread的类继承结构如下: Class hierarchy Thread NamedThread VMThread ConcurrentGCThread WorkerThread GangWorker GCTaskThread JavaThread WatcherThread 另外还有一个重要的类OSThread不在这个继承关系里,它以组合的方式被Thread类所使用 这些类构成了JVM的线程模型,其中最主要的是下面几个类: java.lang.Thread: 这个是Java语言里的线程类,由这个Java类创建的instance都会 1:1 映射到一个操作系统的 osthread JavaThread: JVM中C++定义的类,一个JavaThread的instance代表了在JVM中的java.lang.Thread的instance, 它维护了线程的状态,并且维护一个指针指向java.lang.Thread创建的对象(oop)。它同时还维护了一个指针指向对应的OSThread,来获取底层操作系统创建的osthread的状态 OSThread: JVM中C++定义的类,代表了JVM中对底层操作系统的osthread的抽象,它维护着实际操作系统创建的线程句柄handle,可以获取底层osthread的状态 VMThread: JVM中C++定义的类,这个类和用户创建的线程无关,是JVM本身用来进行虚拟机操作的线程,比如GC。 有两种方式可以让用户在JVM中创建线程 new java.lang.Thread().start() 使用JNI将一个native thread attach到JVM中 针对 new java.lang.Thread().start()这种方式,只有调用start()方法的时候,才会真正的在JVM中去创建线程,主要的生命周期步骤有: 创建对应的JavaThread的instance 创建对应的OSThread的instance 创建实际的底层操作系统的native thread 准备相应的JVM状态,比如ThreadLocal存储空间分配等 底层的native thread开始运行,调用java.lang.Thread生成的Object的run()方法 当java.lang.Thread生成的Object的run()方法执行完毕返回后,或者抛出异常终止后,终止native thread 释放JVM相关的thread的资源,清除对应的JavaThread和OSThread 针对JNI将一个native thread attach到JVM中,主要的步骤有: 通过JNI call AttachCurrentThread申请连接到执行的JVM实例 JVM创建相应的JavaThread和OSThread对象 创建相应的java.lang.Thread的对象 一旦java.lang.Thread的Object创建之后,JNI就可以调用Java代码了 当通过JNI call DetachCurrentThread之后,JNI就从JVM实例中断开连接 JVM清除相应的JavaThread, OSThread, java.lang.Thread对象 从JVM的角度来看待线程状态的状态有以下几种: 其中主要的状态是这5种: _thread_new: 新创建的线程 _thread_in_Java: 在运行Java代码 _thread_in_vm: 在运行JVM本身的代码 ...

2013-07-27 · 1 min · 111 words · -

ReentrantReadWriteLock

ReentrantReadWriteLock 一、ReentrantReadWriteLock与ReentrantLock 说到ReentrantReadWriteLock,首先要做的是与ReentrantLock划清界限。它和后者都是单独的实现,彼此之间没有继承或实现的关系。 ReentrantLock 实现了标准的互斥操作,也就是一次只能有一个线程持有锁,也即所谓独占锁的概念。显然这个特点在一定程度上面减低了吞吐量,实际上独占锁是一种保守的锁策略,在这种情况下任何"读/读",“写/读”,“写/写"操作都不能同时发生。但是同样需要强调的一个概念是,锁是有一定的开销的,当并发比较大的时候,锁的开销就比较可观了。所以如果可能的话就尽量少用锁,非要用锁的话就尝试看能否改造为读写锁。 ReadWriteLock 描述的是: 一个资源能够被多个读线程访问,或者被一个写线程访问,但是不能同时存在读写线程。也就是说读写锁使用的场合是一个共享资源被大量读取操作,而只有少量的写操作 (修改数据) 。清单0描述了ReadWriteLock的API。 // 清单0 ReadWriteLock 接口 public interface ReadWriteLock { Lock readLock(); Lock writeLock(); } 清单0描述的ReadWriteLock结构,这里需要说明的是ReadWriteLock并不是Lock的子接口,只不过ReadWriteLock借助Lock来实现读写两个视角。在ReadWriteLock中每次读取共享数据就需要读取锁,当需要修改共享数据时就需要写入锁。看起来好像是两个锁,但其实不尽然,下文会指出。 二、ReentrantReadWriteLock的特性 ReentrantReadWriteLock有以下几个特性: 公平性 非公平锁 (默认) 这个和独占锁的非公平性一样,由于读线程之间没有锁竞争,所以读操作没有公平性和非公平性,写操作时,由于写操作可能立即获取到锁,所以会推迟一个或多个读操作或者写操作。因此非公平锁的吞吐量要高于公平锁。 公平锁利用AQS的CLH队列,释放当前保持的锁 (读锁或者写锁) 时,优先为等待时间最长的那个写线程分配写入锁,当前前提是写线程的等待时间要比所有读线程的等待时间要长。同样一个线程持有写入锁或者有一个写线程已经在等待了,那么试图获取公平锁的 (非重入) 所有线程 (包括读写线程) 都将被阻塞,直到最先的写线程释放锁。如果读线程的等待时间比写线程的等待时间还有长,那么一旦上一个写线程释放锁,这一组读线程将获取锁。 重入性 读写锁允许读线程和写线程按照请求锁的顺序重新获取读取锁或者写入锁。当然了只有写线程释放了锁,读线程才能获取重入锁。 写线程获取写入锁后可以再次获取读取锁,但是读线程获取读取锁后却不能获取写入锁。 另外读写锁最多支持65535个递归写入锁和65535个递归读取锁。 锁降级 写线程获取写入锁后可以获取读取锁,然后释放写入锁,这样就从写入锁变成了读取锁,从而实现锁降级的特性。 锁升级 读取锁是不能直接升级为写入锁的。因为获取一个写入锁需要释放所有读取锁,所以如果有两个读取锁视图获取写入锁而都不释放读取锁时就会发生死锁。 锁获取中断 读取锁和写入锁都支持获取锁期间被中断。这个和独占锁一致。 条件变量 写入锁提供了条件变量(Condition)的支持,这个和独占锁一致,但是读取锁却不允许获取条件变量,将得到一个UnsupportedOperationException异常。 重入数 读取锁和写入锁的数量最大分别只能是65535 (包括重入数) 。 三、ReentrantReadWriteLock的内部实现 3.1 读写锁是独占锁的两个不同视图 ReentrantReadWriteLock里面的锁主体就是一个Sync,也就是上面提到的FairSync或者NonfairSync,所以说实际上只有一个锁,只是在获取读取锁和写入锁的方式上不一样,所以前面才有读写锁是独占锁的两个不同视图一说。 ReentrantReadWriteLock里面有两个类: ReadLock/WriteLock,这两个类都是Lock的实现。 // 清单1 ReadLock 片段 public static class ReadLock implements Lock, java.io.Serializable { private final Sync sync; protected ReadLock(ReentrantReadWriteLock lock) { sync = lock.sync; } public void lock() { sync.acquireShared(1); } public void lockInterruptibly() throws InterruptedException { sync.acquireSharedInterruptibly(1); } public boolean tryLock() { return sync.tryReadLock(); } public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); } public void unlock() { sync.releaseShared(1); } public Condition newCondition() { throw new UnsupportedOperationException(); } } //清单2 WriteLock 片段 public static class WriteLock implements Lock, java.io.Serializable { private final Sync sync; protected WriteLock(ReentrantReadWriteLock lock) { sync = lock.sync; } public void lock() { sync.acquire(1); } public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); } public boolean tryLock( ) { return sync.tryWriteLock(); } public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); } public void unlock() { sync.release(1); } public Condition newCondition() { return sync.newCondition(); } public boolean isHeldByCurrentThread() { return sync.isHeldExclusively(); } public int getHoldCount() { return sync.getWriteHoldCount(); } } 清单1描述的是读锁的实现,清单2描述的是写锁的实现。显然WriteLock就是一个独占锁,这和ReentrantLock里面的实现几乎相同,都是使用了AQS的acquire/release操作。当然了在内部处理方式上与ReentrantLock还是有一点不同的。对比清单1和清单2可以看到,ReadLock获取的是共享锁,WriteLock获取的是独占锁。 ...

2013-07-13 · 4 min · 852 words · -

java 代码块

java 代码块 在编程过程中我们可能会遇到如下这种形式的程序: public class Test { { //... } } 这种形式的程序段我们将其称之为代码块,所谓代码块就是用大括号({})将多行代码封装在一起,形成一个独立的数据体,用于实现特定的算法。一般来说代码块是不能单独运行的,它必须要有运行主体。在Java中代码块主要分为四种: 一、 普通代码块 普通代码块是我们用得最多的也是最普遍的,它就是在方法名后面用{}括起来的代码片段。普通代码块是不能够单独存在的,它必须要紧跟在方法名后面。同时也必须要使用方法名调用它。 public class Test { public void test(){ System.out.println(“普通代码块”); } } 二、 静态代码块 想到静态我们就会想到static,静态代码块就是用static修饰的用{}括起来的代码片段,它的主要目的就是对静态属性进行初始化。 public class Test { static{ System.out.println(“静态代码块”); } } 三、 同步代码块 使用 synchronized 关键字修饰,并使用"{}"括起来的代码片段,它表示同一时间只能有一个线程进入到该方法块中,是一种多线程保护机制。 四、 构造代码块 在类中直接定义没有任何修饰符、前缀、后缀的代码块即为构造代码块。我们明白一个类必须至少有一个构造函数,构造函数在生成对象时被调用。构造代码块和构造函数一样同样是在生成一个对象时被调用,那么构造代码在什么时候被调用?如何调用的呢?看如下代码: 复制代码 public class Test { /** 构造代码 */ { System.out.println(“执行构造代码块…”); } /** * 无参构造函数 */ public Test(){ System.out.println("执行无参构造函数..."); } /** * 有参构造函数 * @param id id */ public Test(String id){ System.out.println("执行有参构造函数..."); } } ...

2013-06-27 · 2 min · 215 words · -

AbstractQueuedSynchronizer, AQS

AbstractQueuedSynchronizer, AQS http://blog.zhangjikai.com/2017/04/15/%E3%80%90Java-%E5%B9%B6%E5%8F%91%E3%80%91%E8%AF%A6%E8%A7%A3-AbstractQueuedSynchronizer/ 队列同步器 AbstractQueuedSynchronizer (以下简称 AQS) ,是用来构建锁或者其他同步组件的基础框架。它使用一个 int 成员变量来表示同步状态,通过 CAS 操作对同步状态进行修改,确保状态的改变是安全的。通过内置的 FIFO (First In First Out) 队列来完成资源获取线程的排队工作。更多关于 Java 多线程的文章可以转到 这里 AQS 和 synchronized 在介绍 AQS 的使用之前,需要首先说明一点,AQS 同步和 synchronized 关键字同步 (以下简称 synchronized 同步) 是采用的两种不同的机制。首先看下 synchronized 同步,synchronized 关键字经过编译之后,会在同步块的前后分别形成 monitorenter 和 monitorexit 这两个字节码指令,这两个字节码需要关联到一个监视对象,当线程执行 monitorenter 指令时,需要首先获得获得监视对象的锁,这里监视对象锁就是进入同步块的凭证,只有获得了凭证才可以进入同步块,当线程离开同步块时,会执行 monitorexit 指令,释放对象锁。 在 AQS 同步中,使用一个 int 类型的变量 state 来表示当前同步块的状态。以独占式同步 (一次只能有一个线程进入同步块) 为例,state 的有效值有两个 0 和 1,其中 0 表示当前同步块中没有线程,1 表示同步块中已经有线程在执行。当线程要进入同步块时,需要首先判断 state 的值是否为 0,假设为 0,会尝试将 state 修改为 1,只有修改成功了之后,线程才可以进入同步块。注意上面提到的两个条件: state 为 0,证明当前同步块中没有线程在执行,所以当前线程可以尝试获得进入同步块的凭证,而这里的凭证就是是否成功将 state 修改为 1 (在 synchronized 同步中,我们说的凭证是对象锁,但是对象锁的最终实现是否和这种方式类似,没有找到相关的资料) ...

2013-05-15 · 17 min · 3619 words · -

Java 常量池

Java 常量池 什么是常量 用final修饰的成员变量表示常量,值一旦给定就无法改变! final修饰的变量有三种: 静态变量、实例变量和局部变量,分别表示三种类型的常量。 Class 文件中的常量池 在Class文件结构中,最头的4个字节用于存储魔数 Magic Number,用于确定一个文件是否能被JVM接受,再接着4个字节用于存储版本号,前2个字节存储次版本号,后2个存储主版本号,再接着是用于存放常量的常量池,由于常量的数量是不固定的,所以常量池的入口放置一个U2类型的数据(constant_pool_count)存储常量池容量计数值。 常量池主要用于存放两大类常量: 字面量(Literal)和符号引用量(Symbolic References),字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等,符号引用则属于编译原理方面的概念,包括了如下三种类型的常量: 类和接口的全限定名 字段名称和描述符 方法名称和描述符 方法区中的运行时常量池 运行时常量池是方法区的一部分。 Class文件常量池 CLass文件的字节码包含有类的版本、字段、方法、接口等描述信息,还有就是常量池 常量池里面主要存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。 运行时常量池相对于CLass文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入CLass文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的就是String类的intern()方法。 常量池中主要存放两类常量。 字面量。 符号引用。 字面量 字面量,给基本类型变量赋值的方式就叫做字面量或者字面值。 比如:String a=“b” ,这里“b”就是字符串字面量,同样类推还有整数字面值、浮点类型字面量、字符字面量。 符号引用 符号引用主要设涉及编译原理方面的概念,包括下面三类常量: 类和接口的全限定名(Full Qualified Name),也就是Ljava/lang/String;,主要用于在运行时解析得到类的直接引用。 字段的名称和描述符(Descriptor),字段也就是类或者接口中声明的变量,包括类级别变量(static)和实例级的变量。 方法的名称和描述符,方法的描述类似于JNI动态注册时的“方法签名”,也就是参数类型+返回值类型,比如下面的这种字节码,表示main方法和String返回类型。 运行时常量池 运行时常量池是每一个类或者接口的常量池 (Constant Pool)的运行时的表现形式。 我们知道,一个类的加载过程,会经过:加载、连接 (验证、准备、解析)、初始化的过程,而在类加载这个阶段,需要做以下几件事情: 通过一个类的全类限定名获取此类的二进制字节流。 在堆内存生成一个java.lang.Class对象,代表加载这个类,做为这个类的入口。 将class字节流的静态存储结构转化成方法区(元空间)的运行时数据结构。 而其中第三点,将class字节流代表的静态储存结构转化为方法区的运行时数据结构这个过程,就包含了class文件常量池进入运行时常量池的过程。 所以,运行时常量池的作用是存储class文件常量池中的符号信息,在类的解析阶段会把这些符号引用转换成直接引用(实例对象的内存地址),翻译出来的直接引用也是存储在运行时常量池中。class文件常量池的大部分数据会被加载到运行时常量池。 字符串常量池 字符串常量池,简单来说就是专门针对String类型设计的常量池。 字符串常量池的常用创建方式有两种。 String a=“Hello”; String b=new String(“Mic”); a这个变量,是在编译期间就已经确定的,会进入到字符串常量池。 b这个变量,是通过new关键字实例化,new是创建一个对象实例并初始化该实例,因此这个字符串对象是在运行时才能确定的,创建的实例在堆空间上。 简单总结一下:JVM之所以单独设计字符串常量池,是JVM为了提高性能以及减少内存开销的一些优化: String对象作为Java语言中重要的数据类型,是内存中占据空间最大的一个对象。高效地使用字符串,可以提升系统的整体性能。 创建字符串常量时,首先检查字符串常量池是否存在该字符串,如果有,则直接返回该引用实例,不存在,则实例化该字符串放入常量池中。 字符串常量池是JVM所维护的一个字符串实例的引用表,在HotSpot VM中,它是一个叫做StringTable的全局表。在字符串常量池中维护的是字符串实例的引用,底层C++实现就是一个Hashtable。这些被维护的引用所指的字符串实例,被称作”被驻留的字符串”或”interned string”或通常所说的”进入了字符串常量池的字符串”! 封装类常量池 除了字符串常量池,Java的基本类型的封装类大部分也都实现了常量池。包括Byte,Short,Integer,Long,Character,Boolean 注意,浮点数据类型Float,Double是没有常量池的。 封装类的常量池是在各自内部类中实现的,比如IntegerCache(Integer的内部类)。要注意的是,这些常量池是有范围的: 常量池的好处 常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。 ...

2013-03-06 · 3 min · 573 words · -

DelayQueue

DelayQueue DelayQueue 是什么 DelayQueue 是一个无界的 BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期时间最长。注意:不能将null元素放置到这种队列中。 二、DelayQueue 能做什么 1. 淘宝订单业务: 下单之后如果三十分钟之内没有付款就自动取消订单。 2. 饿了吗订餐通知: 下单成功后60s之后给用户发送短信通知。 3.关闭空闲连接。服务器中,有很多客户端的连接,空闲一段时间之后需要关闭之。 4.缓存。缓存中的对象,超过了空闲时间,需要从缓存中移出。 5.任务超时处理。在网络协议滑动窗口请求应答式交互时,处理超时未响应的请求等。 三、实例展示 定义元素类,作为队列的元素 DelayQueue只能添加(offer/put/add)实现了Delayed接口的对象,意思是说我们不能想往DelayQueue里添加什么就添加什么,不能添加int、也不能添加String进去,必须添加我们自己的实现了Delayed接口的类的对象,来代码 https://www.cnblogs.com/myseries/p/10944211.html DelayQueue基本原理 DelayQueue是一个没有边界BlockingQueue实现,加入其中的元素必需实现Delayed接口。当生产者线程调用put之类的方法加入元素时,会触发Delayed接口中的compareTo方法进行排序,也就是说队列中元素的顺序是按到期时间排序的,而非它们进入队列的顺序。排在队列头部的元素是最早到期的,越往后到期时间赿晚。 消费者线程查看队列头部的元素,注意是查看不是取出。然后调用元素的getDelay方法,如果此方法返回的值小0或者等于0,则消费者线程会从队列中取出此元素,并进行处理。如果getDelay方法返回的值大于0,则消费者线程wait返回的时间值后,再从队列头部取出元素,此时元素应该已经到期。 DelayQueue是Leader-Followr模式的变种,消费者线程处于等待状态时,总是等待最先到期的元素,而不是长时间的等待。消费者线程尽量把时间花在处理任务上,最小化空等的时间,以提高线程的利用效率。 以下通过队列及消费者线程状态变化大致说明一下DelayQueue的运行过程。 ———————————————— 版权声明:本文为CSDN博主「五星上炕」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/dkfajsldfsdfsd/article/details/88966814

2013-02-25 · 1 min · 31 words · -

String#intern, String.intern()

String#intern, String.intern() public class StringIntern { public static void main(String[] args) { String s0 = "foo"; String s1 = "foo"; String s2 = new String("foo"); String s3 = s2.intern(); String s4 = new String("foo").intern(); String s5 = new String("s5"); System.out.println(s0==s1); System.out.println(s0==s2); System.out.println(s0==s3); System.out.println(s2==s3); System.out.println(s0==s4); System.out.println(s0==s5); } } true false true false true false 在Java8中,String类维护了一个字符串常量池 (注意此常量池在运行期间位于堆中),当调用intern方法时,首先在常量池中查看是否已有相同的字符串 (字符串是否相同使用String的equal方法判断),如果常量池中已有,则直接返回该字符串的引用,如果没有,则将当前字符串对象加入常量池中,并返回当前字符串的引用。 ...

2013-02-22 · 7 min · 1311 words · -

jib-maven-plugin

jib-maven-plugin Jib 是一个构建 Docker 或者 OCI 镜像的 Maven 插件 https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin <plugin> <groupId>com.google.cloud.tools</groupId> <artifactId>jib-maven-plugin</artifactId> <version>3.2.1</version> <configuration> <from> <image>openjdk:17.0.2-jdk</image> </from> <to> <image>${docker.prefix}foo/${project.name}:${project.version}</image> </to> <container> <environment> <TZ>Asia/Shanghai</TZ> </environment> <jvmFlags> <jvmFlag>-Xms128m</jvmFlag> </jvmFlags> <mainClass>${project.main.class}</mainClass> <creationTime>USE_CURRENT_TIMESTAMP</creationTime> </container> </configuration> </plugin>

2013-02-21 · 1 min · 33 words · -

PriorityBlockingQueue

PriorityBlockingQueue 这就是带优先级的无界阻塞队列,每次出队都返回优先级最高或者最低的元素(这里规则可以自己制定),内部是使用平衡二叉树实现的,遍历不保证有序; https://www.cnblogs.com/wyq1995/p/12289462.html

2013-02-19 · 1 min · 3 words · -

shiro tag

shiro tag <shiro:principal/> - Displays the user's principal or a property of the user's principal.

2013-02-03 · 1 min · 15 words · -

spring MVC json

spring MVC json spring mvc 返回json格式数据的方式 http://blog.csdn.net/skmbw/article/details/12263253 <dependency> <groupId>org.codehaus.jackson</groupId> jackson-core-lgpl</artifactId> <version>1.9.11</version> </dependency> <dependency> <groupId>org.codehaus.jackson</groupId> jackson-mapper-lgpl</artifactId> <version>1.9.11</version> </dependency>

2013-02-02 · 1 min · 17 words · -

Java方法返回多个值

Java方法返回多个值 http://hi.baidu.com/xiaoyoue/item/7b905356a33d51948d12ed53 我最初学java用的教材就是《java编程思想》,觉得这本书很多知识讲的很细、很透彻,但对一些知识点不理解,有些是没有读懂,有些是觉得这些知识点 与主要内容不相关。当时项目紧,对这些不理解的知识就过去了,反正已经可以编程了。做了2年的java开发之后,重读这本书的,才发现这些不相关知识的内 在联系,如果当时理会了作者的意图,工作中将会少走很多的弯路。 我重读第12章时,想起自己解决一个问题走了很多的弯路,而在这章都做了详细的讨论与说明,把这个问题拿出来,按我走过的弯路,每一步都讨论一下,希望初学者少走些弯路。 问题一: 在写程序的时候,希望某个方法有两个返回值,如何处理? 讨 论一: 用过C语言的人都知道,其中一个可以通过返回值返回,另一个可以通过一个指针参数返回 (将一个指针变量传入函数,改变指针指向的内容,达到目的) 。 Java没有指针,如何达到这个目的?12章的一段话说的很明白: “事实上,Java中每个对象 (除基本数剧类型以外) 的标识符都属于指针的一种。但它们 的使用受到了严格的限制和规范…"。是否可以利用这点返回多个值?回答是肯定的,java.io.InputStream 类的read方法就是这么用的,参见public int read(byte[] b)。 收获一: java方法的参数传递方法有两 种: 1、按值传递: java的基本数据类型,都是采用的这种方式;2、按引用传递,其它类型的对象,都是传递引用 (句柄) 。传递引用的方式有一个问题,即 两个引用指向同一个对象,在函数中修改了一个对象,同时也修改了函数外的那个对象,即容易引起别名的副作用。 结论一: 在一些情况下,可以利用别名的副作用,达到返回多个返回值的目的。如java.io.InputStream类的read方法。 问题二: 根据结论一,对于自定义的一些类,一般可以作为输出参数,达到返回值的目的,但对于基本数据类型,如何解决呢? 讨 论二: 既然每一个数据基本类型都对应一个包装类,是否可以使用相应的包装类,来达到目的呢?实践发现,这些包装类的对象是没有办法修改的,都是只读类。这 些类的对象,一旦创建,便不能修改,不能使用包装类达到目的。这种情况下,发现用数组可以达到目的,可以创建一个基本类型数据的数组,作为参数传递到方法 中,在方法中改变数组内容,达到目的。 收获二: 发现有些类是只读类,这些类的对象一旦创建,就不能修改;基本数据类型的包装类都是只读 类。这些类实际上使用了《java与 模式》一书中提到的不变模式。不变模式的优点: 1、不变的对象比可变对象更加容易维护;2、线程安全。缺点: 一旦需要修改一个不变的对象,就必须创建一个 新的对象。一些关于String和StringBuffer的区别的讨论,无非就是String采用的是不变模式,而StringBuffer没有采用不 变模式,本质上就是不变模式的讨论而已。 结论二: 对于基本数剧类型作为方法的输出参数的变通手段: 采用基本数据类型的数组可以达到目的,对于基本数据类型的包装类,则达不到目的。 (为了将一个基本数据类型作为输出参数就创建一个数组,只是达到目的的一种方式,不值得推荐使用。) 问题三: 应用结论二,在RMI调用时,输出参数在方法调用时改变了,而在调用处却没有改变。为什么? 讨 论三: 我在写一个rmi服务接口时,采用以下的方法接口: byte[] getFaxByte(Fax fax, int[] page),其中 page作为输出参数,希望在调用时,函数改变page[0]的值,在函数调用过后,通过对page的引用得到修改后的值。在实践中我发现,rmi的服务 和调用在同一台机器上,可以得到正确结果,而rmi服务和调用不在同一台机器上,结果便不正确。 经过单步跟踪,发现了问题根源,原来是自己对rmi的原理不熟,没有理解rmi的基础。 收 获三: rmi是远程方法调用,本质上就是为了让一个JVM能够调用另外一个JVM的方法。Rmi的基本原理是利用stub和skel将远程对象伪装成自己 机器内的某个本地对象。容易忽略的一点: 远程接口的参数、返回参数必须实现Serializable接口;在远程调用时,将参数通过 Serializable传到远程的服务器上,而将远程的返回结果Serializable传回到调用的机器上。在这种情况下,由于参数在方法调用处和方 法的处理引用的不是同一个对象,因而便不会有结论一中提到的别名的副作用,在rmi调用,使用输出参数是不会成功的。 ...

2013-01-29 · 1 min · 100 words · -

java char[] 转 String

‘java char[] 转 String’ public class Test{ public static void main(String[] args){ char[] data={a,b,c}; String s=new String(data); System.out.println(s); char[] chars = s.toCharArray(); } }

2013-01-24 · 1 min · 24 words · -

Mybatis多参数查询映射

Mybatis多参数查询映射 http://fengfan876.iteye.com/blog/1473863 最近在做一个Mybatis的项目,由于是接触不久,虽然看了一下资料,但在实际开发中还是暴露了很多问题,其中最让我头疼的就是selete的parameterType问题,网上这类的帖子虽然有但是不全,很多情况下很难找到你想要的答案。 为此我对这个问题进行了总结,希望对像我这样的新手有一定的帮助。 (一) 单个参数 public List getXXBeanList(String xxCode); select 字段… from XXX where code = #{xxCode} 其中方法名和ID一致,#{}中的参数名与方法中的参数名一直, 我这里采用的是XXXBean是采用的短名字, select 后的字段列表要和bean中的属性名一致, 如果不一致的可以用 as 来补充。 (二) 多参数 public List getXXXBeanList(String xxId, String xxCode); select 字段… from XXX where id=#{0} code = #{1} 由于是多参数那么就不能使用parameterType, 改用#{index}是第几个就用第几个的索引,索引从0开始 ...

2013-01-24 · 1 min · 98 words · -