若是volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,由于它不会引发线程上下文的切换和调度。html
若是对声明了volatile的变量进行写操做,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存java
每一个处理器经过嗅探在总线上传播的数据来检查本身缓存的值是否是过时了,当处理器发现本身缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操做的时候,会从新从系统内存中把数据读处处理器缓存里。程序员
volatile实现原则算法
jdk 7追加字节优化性能编程
将共享变量追加到64字节。一些处理器不支持部分填充缓存行,若是队列头节点和尾节点都不足64字节的话,处理器会将他们读到同一个高速缓存行中,在多处理器下每一个处理器都会缓存一样的头、尾节点,当一个处理器试图修改头节点时,会将整个缓存行锁定,那么在缓存一致性机制的做用下,会致使其余处理器不能访问本身高速缓存中的尾节点,而队列的入队和出队操做则须要不停修改头节点和尾节点,因此在多处理器的状况下将会严重影响到队列的入队和出队效率。Doug lea使用追加到64字节的方式来填满高速缓冲区的缓存行,避免头节点和尾节点加载到同一个缓存行,使头、尾节点在修改时不会互相锁定。数组
偏向锁:当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,之后该线程在进入和退出同步块时不须要进行CAS操做来加锁和解锁,只需简单地测试一下对象头的Mark Word
里是否存储着指向当前线程的偏向锁。若是测试成功,表示线程已经得到了锁。若是测试失败,则须要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):若是没有设置,则使用CAS竞争锁;若是设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。缓存
轻量级锁:线程在执行同步块以前,JVM会先在当前线程的栈桢中建立用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。而后线程尝试使用CAS将对象头中的Mark
Word替换为指向锁记录的指针。若是成功,当前线程得到锁,若是失败,表示其余线程竞争锁,当前线程便尝试使用自旋来获取锁。安全
总线锁:线锁就是使用处理器提供的一个LOCK #信号,当一个处理器在总线上输出此信号时,其余处理器的请求将被阻塞住,那么该处理器能够独占共享内存。微信
缓存锁:指内存区域若是被缓存在处理器的缓存行中,而且在Lock操做期间被锁定,那么当它执行锁操做回写到内存时,处理器不在总线上声言LOCK #信号,而是修改内部的内存地址,并容许它的缓存一致性机制来保证操做的原子性,由于缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据,当其余处理器回写已被锁定的缓存行的数据时,会使缓存行无效。多线程
两种状况不会使用缓存锁
CAS 原子操做的问题
ABA问题:可是若是一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,可是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加1,那么A→B→A就会变成1A→2B→3A。
循环时间长开销大问题。自旋CAS若是长时间不成功,会给CPU带来很是大的执行开销。
只能保证一个共享变量的原子操做。还有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操做。好比,有两个共享变量i=2,j=a,合并一下ij=2a,而后用CAS来操做ij。
使用锁机制实现原子操做锁机制保证了只有得到锁的线程才可以操做锁定的内存区域。JVM内部实现了不少种锁机制,有偏向锁、轻量级锁和互斥锁。有意思的是除了偏向锁,JVM实现锁的方式都用了循环CAS,即当一个线程想进入同步块的时候使用循环CAS的方式来获取锁,当它退出同步块的时候使用循环CAS释放锁。
在执行程序时,为了提升性能,编译器和处理器经常会对指令作重排序。重排序分3种类型
1属于编译器重排序,2和3属于处理器重排序;
对于编译器,JMM的编译器重排序规则会禁止特定类型的编译器重排序(不是全部的编译器重排序都要禁止)。对于处理器重排序,JMM的处理器重排序规则会要求Java编译器在生成指令序列时,插入特定类型的内存屏障(Memory Barriers,Intel称之为Memory Fence)指令,经过内存屏障指令来禁止特定类型的处理器重排序。
经过以批处理的方式刷新写缓冲区,以及合并写缓冲区中对同一内存地址的屡次写,减小对内存总线的占用。虽然写缓冲区有这么多好处,但每一个处理器上的写缓冲区,仅仅对它所在的处理器可见。这个特性会对内存操做的执行顺序产生重要的影响:处理器对内存的读/写操做的执行顺序,不必定与内存实际发生的读/写操做顺序一致!
sparc-TSO和X86拥有相对较强的处理器内存模型,它们仅容许对写-读操做作重排序
StoreLoad Barriers是一个“全能型”的屏障,它同时具备其余3个屏障的效果。
执行该屏障开销会很昂贵,由于当前处理器一般要把写缓冲区中的数据所有刷新到内存中(Buffer Fully Flush)。
java使用新的JSR-133内存模型。在JMM中若是一个操做执行的结果须要对另外一个操做可见,那么这两个操做之间必需要在happens-before关系。
与程序员密切相关的happens-before规则以下。
程序顺序规则:一个线程中的每一个操做,happens-before于该线程中的任意后续操做。
监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
volatile变量规则:对一个volatile域的写,happens- before于任意后续对这个volatile域的读。
传递性:若是A happens-before B,且B happens-before C,那么Ahappens-before C。
一个线程中的全部操做必须按照程序的顺序来执行。
(无论程序是否同步)全部线程都只能看到一个单一的操做执行顺序。在顺序一致性内存模型中,每一个操做都必须原子执行且马上对全部线程可见。
当多个线程并发执行时,图中的开关装置能把全部线程的全部内存读/写操做串行化(即在顺序一致性模型中,全部操做之间具备全序关系)。
总线事务包括读事务(Read Transaction)和写事务(Write Transaction)。读事务从内存传送数据处处理器,写事务从处理器传送数据到内存,每一个事务会读/写内存中一个或多个物理上连续的字。
在一个处理器执行总线事务期间,总线会禁止其余的处理器和I/O设备执行内存的读/写。
当JVM在这种处理器上运行时,可能会把一个64位long/double型变量的写操做拆分为两个32位的写操做来执行。这两个32位的写操做可能会被分配到不一样的总线事务中执行,此时对这个64位变量的写操做将不具备原子性。
从JSR -133内存模型开始(即从JDK5开始),仅仅只容许把一个64位long/double型变量的写操做拆分为两个32位的写操做来执行,任意的读操做在JSR-133中都必须具备原子性(即任意读操做必需要在单个读事务中执行)。
可见性。对一个volatile变量的读,老是能看到(任意线程)对这个volatile变量最后的写入。
原子性:对任意单个volatile变量的读/写具备原子性,但相似于volatile++这种复合操做不具备原子性。
每个箭头连接的两个节点,表明了一个happens-before关系。黑色箭头表示程序顺序规则;橙色箭头表示volatile规则;蓝色箭头表示组合这些规则后提供的happens-before保证。
A线程写一个volatile变量后,B线程读同一个volatile变量。A线程在写volatile变量以前全部可见的共享变量(即写以前的值都写入到JMM中),在B线程读同一个volatile变量后,将当即变得对B线程可见。
线程A写一个volatile变量,随后线程B读这个volatile变量,这个过程实质上是线程A经过主内存向线程B发送消息。
StoreLoad屏障:一个写线程写volatile变量,多个读线程读同一个volatile变量。当读线程的数量大大超过写线程时,选择在volatile写以后插入StoreLoad屏障将带来可观的执行效率的提高。
公平锁和非公平锁释放时,最后都要写一个volatile变量state。
公平锁获取时,首先会去读volatile变量。
非公平锁获取时,首先会用CAS更新volatile变量,这个操做同时具备volatile读和volatile写的内存语义。
在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操做之间不能重排序。
初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操做之间不能重排序
假设一个线程A执行writer()方法,随后另外一个线程B执行reader()方法
读到普通变量初始化以前的值
对象的普通域的操做被处理器重排序到读对象引用以前。读普通域时,该域尚未被写线程A写入,这是一个错误的读取操做。而读final域的重排序规则会把读对象final域的操做“限定”在读对象引用以后,此时该final域已经被A线程初始化过了,这是一个正确的读取操做。
在构造函数内对一个final引用的对象的成员域的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操做之间不能重排序。
1是对final域的写入,2是对这个final域引用的对象的成员域的写入,3是把被构造的对象的引用赋值给某个引用变量。这里除了前面提到的1不能和3重排序外,2和3也不能重排序。
JMM能够确保读线程C至少能看到写线程A在构造函数中对final引用对象的成员域的写入。即C至少能看到数组下标0的值为1。而写线程B对数组元素的写入,读线程C可能看获得,也可能看不到。JMM不保证线程B的写入对读线程C
可见,由于写线程B和读线程C之间存在数据竞争,此时的执行结果不可预知。
若是想要确保读线程C看到写线程B对数组元素的写入,写线程B和读线程C之间须要使用同步原语(lock或volatile)来确保内存可见性。
在引用变量为任意线程可见以前,该引用变量指向的对象的final域已经在构造函数中被正确初始化过了
在构造函数内部,不能让这个被构造对象的引用为其余线程所见,也就是对象引用不能在构造函数中“逸出”。
执行read()方法的线程仍然可能没法看到final域被初始化后的值,由于这里的操做1和操做2之间可能被重排序。
写final域的重排序规则会要求编译器在final域的写以后,构造函数return以前插入一个StoreStore障屏。读final域的重排序规则要求编译器在读final域的操做前面插入一个LoadLoad屏障。因为X86处理器不会对写-写操做作重排序,因此在X86处理器中,写final域须要的StoreStore障屏会被省略掉。一样,因为X86处理器不会对存在间接依赖关系的操做作重排序,因此在X86处理器中,读final域须要的LoadLoad屏障也会被省略掉。也就是说,在X86处理器中,final域的读/写不会插入任何内存屏障!(在x86处理器中仅有StoreLoad屏障)
对于会改变程序执行结果的重排序,JMM要求编译器和处理器必须禁止这种重排序。
对于不会改变程序执行结果的重排序,JMM对编译器和处理器不作要求(JMM容许这种重排序)。
若是一个操做happens-before另外一个操做,那么第一个操做的执行结果将对第二个操做可见,并且第一个操做的执行顺序排在第二个操做以前。
两个操做之间存在happens-before关系,并不意味着Java平台的具体实现必需要按照happens-before关系指定的顺序来执行。若是重排序以后的执行结果,与按happens-before
关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM容许这种重排序)。
相同点:
不一样点:
这里A2和A3虽然重排序了,但Java内存模型的intra-thread semantics将确保A2必定会排在A4前面执行。所以,线程A的intra-thread semantics没有改变,但A2和A3的重排序,将致使线程B在B1处判断出instance不为空,线程B接下来将访问instance引用的对象。此时,线程B将会访问到一个还未初始化的对象。
在知晓了问题发生的根源以后,咱们能够想出两个办法来实现线程安全的延迟初始化。
基于volatile的解决方案
这个方案本质上是经过禁止图3-39中的2和3之间的重排序,来保证线程安全的延迟初始化
基于类初始化的解决方案
在执行类的初始化期间,JVM会去获取一个锁。这个锁能够同步多个线程对同一个类的初始化。
第1阶段:经过在Class对象上同步(即获取Class对象的初始化锁),来控制类或接口的初始化。这个获取锁的线程会一直等待,直到当前线程可以获取到这个初始化锁。
第2阶段:线程A执行类的初始化,同时线程B在初始化锁对应的condition上等待。
第3阶段:线程A设置state=initialized,而后唤醒在condition中等待的全部线程。
第4阶段:线程B结束类的初始化处理。
线程A在第2阶段的A1执行类的初始化,并在第3阶段的A4释放初始化锁;线程B在第4阶段的B1获取同一个初始化锁,并在第4阶段的B4以后才开始访问这个类。根据Java内存模型规范的锁规则,这里将存在以下的happens-before关系。这个happens-before关系将保证:线程A执行类的初始化时的写入操做(执行类的静态初始化和初始化类中声明的静态字段),线程B必定能看到。
第5阶段:线程C执行类的初始化的处理。
在第3阶段以后,类已经完成了初始化。所以线程C在第5阶段的类初始化处理过程相对简单一些(前面的线程A和B的类初始化处理过程都经历了两次锁获取-锁释放,而线程C的类初始化处理只须要经历一次锁获取-锁释放)。线程A在第2阶段的A1执行类的初始化,并在第3阶段的A4释放锁;线程C在第5阶段的C1获取同一个锁,并在在第5阶段的C4以后才开始访问这个类。根据Java内存模型规范的锁规则,将存在以下的happens-before关系。
经过对比基于volatile的双重检查锁定的方案和基于类初始化的方案,咱们会发现基于类初始化的方案的实现代码更简洁。但基于volatile的双重检查锁定的方案有一个额外的优点:除了能够对静态字段实现延迟初始化外,还能够对实例字段实现延迟初始化。
字段延迟初始化下降了初始化类或建立实例的开销,但增长了访问被延迟初始化的字段的开销。在大多数时候,正常的初始化要优于延迟初始化。若是确实须要对实例字段使用线程安全的延迟初始化,请使用上面介绍的基于volatile的延迟初始化的方案;若是确实须要对静态字段使用线程安全的延迟初始化,请使用上面介绍的基于类初始化的方案。
放松程序中写-读操做的顺序,由此产生了Total Store Ordering内存模型(简称为TSO)。
在上面的基础上,继续放松程序中写-写操做的顺序,由此产生了Partial Store Order内存模型(简称为PSO)。
在前面两条的基础上,继续放松程序中读-写和读-读操做的顺序,由此产生了RelaxedMemory Order内存模型(简称为RMO)和PowerPC内存模型。
这里处理器对读/写操做的放松,是以两个操做之间不存在数据依赖性为前提的。
从表3-12中能够看到,全部处理器内存模型都容许写-读重排序,缘由在第1章已经说明过:它们都使用了写缓存区。写缓存区可能致使写-读操做重排序。同时,咱们能够看到这些处理器内存模型都容许更早读到当前处理器的写,缘由一样是由于写缓存区。因为写缓存区仅对当前处理器可见,这个特性致使当前处理器能够比其余处理器先看到临时保存在本身写缓存区中的写。表3-12中的各类处理器内存模型,从上到下,模型由强变弱。越是追求性能的处理器,内存模型设计得会越弱。由于这些处理器但愿内存模型对它们的束缚越少越好,这样它们就能够作尽量多的优化来提升性能。
因为常见的处理器内存模型比JMM要弱,Java编译器在生成字节码时,会在执行指令序列的适当位置插入内存屏障来限制处理器的重排序。同时,因为各类处理器内存模型的强弱不一样,为了在不一样的处理器平台向程序员展现一个一致的内存模型,JMM在不一样的处理器中须要插入的内存屏障的数量和种类也不相同。
JMM屏蔽了不一样处理器内存模型的差别,它在不一样的处理器平台之上为Java程序员呈现了一个一致的内存模型。
JMM是一个语言级的内存模型,处理器内存模型是硬件级的内存模型,顺序一致性内存模型是一个理论参考模型。下面是语言内存模型、处理器内存模型和顺序一致性内存模型的强弱对比示意图,如图3-49所示。
从图中能够看出:常见的4种处理器内存模型比经常使用的3中语言内存模型要弱,处理器内存模型和语言内存模型都比顺序一致性内存模型要弱。同处理器内存模型同样,越是追求执行性能的语言,内存模型设计得会越弱。
单线程程序。单线程程序不会出现内存可见性问题。编译器、runtime和处理器会共同确保单线程程序的执行结果与该程序在顺序一致性模型中的执行结果相同。
正确同步的多线程程序。正确同步的多线程程序的执行将具备顺序一致性(程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同)。这是JMM关注的重点,JMM经过限制编译器和处理器的重排序来为程序员提供内存可见性保证。
未同步/未正确同步的多线程程序。JMM为它们提供了最小安全性保障:线程执行时读取到的值,要么是以前某个线程写入的值,要么是默认值(0、null、false)。
最小安全性保障与64位数据的非原子性写并不矛盾。它们是两个不一样的概念,它们“发生”的时间点也不一样。
最小安全性“发生”在对象被任意线程使用以前。64位数据的非原子性写“发生”在对象被多个线程使用的过程当中(写共享变量)。
64位数据的非原子性写“发生”在对象被多个线程使用的过程当中(写共享变量)。当发生问题时(处理器B看到仅仅被处理器A“写了一半”的无效值),这里虽然处理器B读取到一个被写了一半的无效值,但这个值仍然是处理器A写入的,只不过是处理器A尚未写完而已。
最小安全性保证线程读取到的值,要么是以前某个线程写入的值,要么是默认值(0、null、false)。但最小安全性并不保证线程读取到的值,必定是某个线程写完后的值。最小安全性保证线程读取到的值不会无中生有的冒出来,但并不保证线程读取到的值必定是正确的。
加强volatile的内存语义。旧内存模型容许volatile变量与普通变量重排序。JSR-133严格限制volatile变量与普通变量的重排序,使volatile的写-读和锁的释放-获取具备相同的内存语义。
加强final的内存语义。在旧内存模型中,屡次读取同一个final变量的值可能会不相同。为此,JSR-133为final增长了两个重排序规则。在保证final引用不会从构造函数内逸出的状况下,final具备了初始化安全性。
线程状态
线程状态之间的变化
Daemon线程被用做完成支持性工做,可是在Java虚拟机退出时Daemon线程中的finally块并不必定会执行。
main线程(非Daemon线程)在启动了线程DaemonRunner以后随着main方法执行完毕而终止,而此时Java虚拟机中已经没有非Daemon线程,虚拟机须要退出。Java虚拟机中的全部Daemon线程都须要当即终止,所以DaemonRunner当即终止,可是DaemonRunner中的finally块并无执行。
一个新构造的线程对象是由其parent线程来进行空间分配的,而child线程继承了parent是否为Daemon、优先级和加载资源的contextClassLoader以及可继承的ThreadLocal,同时还会分配一个惟一的ID来标识这个child线程。至此,一个可以运行的线程对象就初始化好了,在堆内存中等待着运行。
线程start()方法的含义是:当前线程(即parent线程)同步告知Java虚拟机,只要线程规划器空闲,应当即启动调用start()方法的线程。
中断比如其余线程对该线程打了个招呼,其余线程经过调用该线程的interrupt()方法对其进行中断操做。
线程经过检查自身是否被中断来进行响应,线程经过方法isInterrupted()来进行判断是否被中断,也能够调用静态方法Thread.interrupted()对当前线程的中断标识位进行复位。若是该线程已经处于终结状态,即便该线程被中断过,在调用该线程对象的isInterrupted()时依旧会返回false。
从Java的API中能够看到,许多声明抛出InterruptedException的方法(例如Thread.sleep(long millis)方法)这些方法在抛出InterruptedException以前,Java虚拟机会先将该线程的中断标识位清除,而后抛出InterruptedException,此时调用isInterrupted()方法将会返回false。
public class Interrupted { public static void main(String[] args) throws Exception { // sleepThread不停的尝试睡眠 Thread sleepThread = new Thread(new SleepRunner(), "SleepThread"); sleepThread.setDaemon(true); // busyThread不停的运行 Thread busyThread = new Thread(new BusyRunner(), "BusyThread"); busyThread.setDaemon(true); sleepThread.start(); busyThread.start(); // 休眠5秒,让sleepThread和busyThread充分运行 TimeUnit.SECONDS.sleep(5); sleepThread.interrupt(); busyThread.interrupt(); System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted()); System.out.println("BusyThread interrupted is " + busyThread.isInterrupted()); // 防止sleepThread和busyThread马上退出 SleepUtils.second(2); } static class SleepRunner implements Runnable { @Override public void run() { while (true) { SleepUtils.second(10); } } } static class BusyRunner implements Runnable { @Override public void run() { while (true) { } } } }
抛出InterruptedException的线程SleepThread,其中断标识位被清除了,而一直忙碌运做的线程BusyThread,中断标识位没有被清除。
本质是对一个对象的监视器(monitor)进行获取,而这个获取过程是排他的,也就是同一时刻只能有一个线程获取到由synchronized所保护对象的监视器。
一个线程对Object(Object由synchronized保护)的访问,首先要得到Object的监视器。若是获取失败,线程进入同步队列,线程状态变为BLOCKED。当访问Object
的前驱(得到了锁的线程)释放了锁,则该释放操做唤醒阻塞在同步队列中的线程,使其从新尝试对监视器的获取。
等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另外一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操做。上述两个线程经过对象O来完成交互,而对象上的wait()和notify/notifyAll()的关系就如同开关信号同样,用来完成等待方和通知方之间的交互工做。
public class WaitNotify { static boolean flag = true; static Object lock = new Object(); public static void main(String[] args) throws Exception { Thread waitThread = new Thread(new Wait(), "WaitThread"); waitThread.start(); TimeUnit.SECONDS.sleep(1); Thread notifyThread = new Thread(new Notify(), "NotifyThread"); notifyThread.start(); } static class Wait implements Runnable { public void run() { // 加锁,拥有lock的Monitor synchronized (lock) { // 当条件不知足时,继续wait,同时释放了lock的锁 while (flag) { try { System.out.println(Thread.currentThread()+ " flagistrue.wait @ " + new SimpleDateFormat("HH:mm:ss").format(new Date())); lock.wait(); } catch (InterruptedException e) { } } // 条件知足时,完成工做 System.out.println(Thread.currentThread() + " flag is false. running @ " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } } } static class Notify implements Runnable { public void run() { // 加锁,拥有lock的Monitor synchronized (lock) { // 获取lock的锁,而后进行通知,通知时不会释放lock的锁, // 直到当前线程释放了lock后,WaitThread才能从wait方法中返回 System.out.println(Thread.currentThread() + " hold lock. notify @ " + new SimpleDateFormat("HH:mm:ss").format(new Date())); lock.notifyAll(); flag = false; SleepUtils.second(5); } // 再次加锁 synchronized (lock) { System.out.println(Thread.currentThread() + " hold lock again. sleep @ " + new SimpleDateFormat("HH:mm:ss").format(new Date())); SleepUtils.second(5); } } } } public class SleepUtils { public static final void second(long seconds) { try { TimeUnit.SECONDS.sleep(seconds); } catch (InterruptedException e){ } } }
调用wait()、notify()以及notifyAll()时须要注意的细节
WaitThread首先获取了对象的锁,而后调用对象的wait()方法,从而放弃了锁并进入了对象的等待队列WaitQueue中,进入等待状态。因为WaitThread释放了对象的锁,NotifyThread随后获取了对象的锁,并调用对象的notify()方法,将WaitThread从WaitQueue移到SynchronizedQueue中,此时WaitThread的状态变为阻塞状态。NotifyThread释放了锁以后,WaitThread再次获取到锁并从wait()方法返回继续执行。
链接池案例 链接数增长则总连接数增长,同时为获取到的比例也在增长
/** * 从链接池中获取、使用和释放链接的过程, * 而客户端获取链接的过程被设定为等待超时的模式, * 也就是在1000毫秒内若是没法获取到可用链接, * 将会返回给客户端一个null。设定链接池的大小为10个, * 而后经过调节客户端的线程数来模拟没法获取链接的场景。 */ public class ConnectionPool { private LinkedList<Connection> pool = new LinkedList<Connection>(); public ConnectionPool(int initialSize) { if (initialSize > 0) { for (int i = 0; i < initialSize; i++) { pool.addLast(ConnectionDriver.createConnection()); } } } public void releaseConnection(Connection connection) { if (connection != null) { synchronized (pool) { // 链接释放后须要进行通知,这样其余消费者可以感知到链接池中已经归还了一个链接 pool.addLast(connection); pool.notifyAll(); } } } // 在mills内没法获取到链接,将会返回null public Connection fetchConnection(long mills) throws InterruptedException { synchronized (pool) { // 彻底超时 if (mills <= 0) { while (pool.isEmpty()) { pool.wait(); } return pool.removeFirst(); } else { long future = System.currentTimeMillis() + mills; long remaining = mills; while (pool.isEmpty() && remaining > 0) { pool.wait(remaining); remaining = future - System.currentTimeMillis(); } Connection result = null; if (!pool.isEmpty()) { result = pool.removeFirst(); } return result; } } } }
/** * 咱们经过动态代理构造了一个Connection,该Connection的代理实现仅仅 * 是在commit()方法调用时休眠100毫秒 */ public class ConnectionDriver { static class ConnectionHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("commit")) { TimeUnit.MILLISECONDS.sleep(100); } return null; } } // 建立一个Connection的代理,在commit时休眠100毫秒 public static final Connection createConnection() { return (Connection) Proxy.newProxyInstance(ConnectionDriver.class.getClassLoader(), new Class<?>[] { Connection.class }, new ConnectionHandler()); } }
/** * 使用了CountDownLatch来确保ConnectionRunnerThread可以同时开始执行, * 而且在所有结束以后,才使main线程从等待状态中返回。 * 当前设定的场景是10个线程同时运行获取链接池(10个链接)中的链接, * 经过调节线程数量来观察未获取到链接的状况 */ public class ConnectionPoolTest { static ConnectionPool pool = new ConnectionPool(10); // 保证全部ConnectionRunner可以同时开始 static CountDownLatch start = new CountDownLatch(1); // main线程将会等待全部ConnectionRunner结束后才能继续执行 static CountDownLatch end; public static void main(String[] args) throws Exception { // 线程数量,能够修改线程数量进行观察 int threadCount = 10; end = new CountDownLatch(threadCount); int count = 20; AtomicInteger got = new AtomicInteger(); AtomicInteger notGot = new AtomicInteger(); for (int i = 0; i < threadCount; i++) { Thread thread = new Thread(new ConnetionRunner(count, got, notGot), "ConnectionRunnerThread"); thread.start(); } start.countDown(); end.await(); System.out.println("total invoke: " + (threadCount * count)); System.out.println("got connection: " + got); System.out.println("not got connection " + notGot); } static class ConnetionRunner implements Runnable { int count; AtomicInteger got; AtomicInteger notGot; public ConnetionRunner(int count, AtomicInteger got, AtomicInteger notGot) { this.count = count; this.got = got; this.notGot = notGot; } public void run() { try { start.await(); } catch (Exception ex) { } while (count > 0) { try { // 从线程池中获取链接,若是1000ms内没法获取到,将会返回null // 分别统计链接获取的数量got和未获取到的数量notGot Connection connection = pool.fetchConnection(1000); if (connection != null) { try { connection.createStatement(); connection.commit(); } finally { pool.releaseConnection(connection); got.incrementAndGet(); } } else { notGot.incrementAndGet(); } } catch (Exception ex) { } finally { count--; } } end.countDown(); } } }
线程池
public class DefaultThreadPool<Job extends Runnable> implements ThreadPool<Job> { // 线程池最大限制数 private static final intMAX_WORKER_NUMBERS = 10; // 线程池默认的数量 private static final int DEFAULT_WORKER_NUMBERS = 5; // 线程池最小的数量 private static final int MIN_WORKER_NUMBERS= 1; // 这是一个工做列表,将会向里面插入工做 private final LinkedList<Job> jobs = new LinkedList<Job>(); // 工做者列表 private final List<Worker> workers = Collections.synchronizedList(new ArrayList<Worker>()); // 工做者线程的数量 private int workerNum = DEFAULT_WORKER_NUMBERS; // 线程编号生成 private AtomicLong threadNum = new AtomicLong(); public DefaultThreadPool() { initializeWokers(DEFAULT_WORKER_NUMBERS); } public DefaultThreadPool(int num) { workerNum = num > MAX_WORKER_NUMBERS ? MAX_WORKER_NUMBERS : num < MIN_WORKER_ NUMBERS ? MIN_WORKER_NUMBERS : num; initializeWokers(workerNum); } public void execute(Job job) { if (job != null) { // 添加一个工做,而后进行通知 synchronized (jobs) { jobs.addLast(job); jobs.notify(); } } } public void shutdown() { for (Worker worker : workers) { worker.shutdown(); } } public void addWorkers(int num) { synchronized (jobs) { // 限制新增的Worker数量不能超过最大值 if (num + this.workerNum > MAX_WORKER_NUMBERS) { num = MAX_WORKER_NUMBERS - this.workerNum; } initializeWokers(num); this.workerNum += num; } } public void removeWorker(int num) { synchronized (jobs) { if (num >= this.workerNum) { throw new IllegalArgumentException("beyond workNum"); } // 按照给定的数量中止Worker int count = 0; while (count < num) { Worker worker = workers.get(count) if (workers.remove(worker)) { worker.shutdown(); count++; } } this.workerNum -= count; } } public int getJobSize() { return jobs.size(); } // 初始化线程工做者 private void initializeWokers(int num) { for (int i = 0; i < num; i++) { Worker worker = new Worker(); workers.add(worker); Thread thread = new Thread(worker, "ThreadPool-Worker-" + threadNum. incrementAndGet()); thread.start(); } } // 工做者,负责消费任务 class Worker implements Runnable { // 是否工做 private volatile boolean running= true; public void run() { while (running) { Job job = null; synchronized (jobs) { // 若是工做者列表是空的,那么就wait while (jobs.isEmpty()) { try { jobs.wait(); } catch (InterruptedException ex) { // 感知到外部对WorkerThread的中断操做,返回 Thread.currentThread().interrupt(); return; } } // 取出一个Job job = jobs.removeFirst(); } if (job != null) { try { job.run(); } catch (Exception ex) { // 忽略Job执行中的Exception } } } } public void shutdown() { running = false; } } }
锁是面向使用者的,它定义了使用者与锁交互的接口(好比能够容许两个线程并行访问),隐藏了实现细节;
同步器面向的是锁的实现者,它简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待与唤醒等底层操做。锁和同步器很好地隔离了使用者和实现者所需关注的领域
所以同步器提供了一个基于CAS的设置尾节点的方法:compareAndSetTail(Node expect, Node update),它须要传递当前线程“认为”的尾节点和当前节点,只有设置成功后,当前节点才正式与以前的尾节点创建关联。
同步队列遵循FIFO,首节点是获取同步状态成功的节点,首节点的线程在释放同步状态时,将会唤醒后继节点,然后继节点将会在获取同步状态成功时将本身设置为首节点,以下图所示
设置首节点是经过获取同步状态成功的线程来完成的,因为只有一个线程可以成功获取到同步状态,所以设置头节点的方法并不须要使用CAS来保证,它只须要将首节点设置成为原首节点的后继节点并断开原首节点的next引用便可。
欢迎关注微信公众号哦~ ~