`
gaozzsoft
  • 浏览: 414625 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类

面霸宝典Notes

 
阅读更多

 

1.MAP

 

HashMap的底层结构实际上是“链表散列”,即数组和链表的结合体。

从上图可以看出,HashMap底层就是一个数组结构(Entry<K,V>[] table),数组中的每一项又是一个链表。

 

HashMap扩容时的分析:

 

       当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,数组扩容这个操作也会出现在ArrayList中,这是一个常用的操作,而在HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。

       那么HashMap什么时候进行扩容呢?当HashMap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

 

从上面的源代码中可以看出:当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。

 

我们可以看到在HashMap中要找到某个元素,需要根据key的hash值来求得对应数组中的位置。如何计算这个位置就是hash算法。前面说过HashMap的数据结构是数组和链表的结合,所以我们当然希望这个HashMap里面的元素位置尽量的分布均匀些,尽量使得每个位置上的元素数量只有一个,那么当我们用hash算法求得这个位置的时候,马上就可以知道对应位置的元素就是我们要的,而不用再去遍历链表,这样就大大优化了查询的效率

        对于任意给定的对象,只要它的 hashCode()返回值相同,那么程序调用 hash(int h)方法所计算得到的hash码值总是相同的。我们首先想到的就是把hash值对数组长度取模运算,这样一来,元素的分布相对来说是比较均匀的。但是,“模”运算的消耗还是比较大的,在HashMap中是这样做的:调用 indexFor(int h, int length) 方法来计算该对象应该保存在 table 数组的哪个索引处。indexFor(int h, int length) 方法的代码如下:

 

从HashMap中get元素时,首先计算key的hashCode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。

  

      归纳起来简单地说,HashMap 在底层将 key-value当成一个整体进行处理,这个整体就是一个 Entry对象。HashMap底层采用一个 Entry[]数组来保存所有的 key-value对,当需要存储一个 Entry对象时,会根据hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。

 

2.ArrayList

ArrayList 是一个数组队列,相当于 动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。
ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。稍后,我们会比较List的“快速随机访问”和“通过Iterator迭代器访问”的效率。
ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。
和Vector不同,ArrayList中的操作不是线程安全的。所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList。

 

3.MQ对比

目前业界有很多MQ产品,我们作如下对比:

RabbitMQ

是使用Erlang编写的一个开源的消息队列,本身支持很多的协议:AMQP,XMPP, SMTP, STOMP,也正是如此,使的它变的非常重量级,更适合于企业级的开发。同时实现了一个经纪人(Broker)构架,这意味着消息在发送给客户端时先在中心队列排队。对路由(Routing),负载均衡(Load balance)或者数据持久化都有很好的支持。

Redis

是一个Key-Value的NoSQL数据库,开发维护很活跃,虽然它是一个Key-Value数据库存储系统,但它本身支持MQ功能,所以完全可以当做一个轻量级的队列服务来使用。对于RabbitMQ和Redis的入队和出队操作,各执行100万次,每10万次记录一次执行时间。测试数据分为128Bytes、512Bytes、1K和10K四个不同大小的数据。实验表明:入队时,当数据比较小时Redis的性能要高于RabbitMQ,而如果数据大小超过了10K,Redis则慢的无法忍受;出队时,无论数据大小,Redis都表现出非常好的性能,而RabbitMQ的出队性能则远低于Redis。

 

入队

出队

 

128B

512B

1K

10K

128B

512B

1K

10K

Redis

16088

15961

17094

25

15955

20449

18098

9355

RabbitMQ

10627

9916

9370

2366

3219

3174

2982

1588

ZeroMQ

号称最快的消息队列系统,尤其针对大吞吐量的需求场景。ZMQ能够实现RabbitMQ不擅长的高级/复杂的队列,但是开发人员需要自己组合多种技术框架,技术上的复杂度是对这MQ能够应用成功的挑战。ZeroMQ具有一个独特的非中间件的模式,你不需要安装和运行一个消息服务器或中间件,因为你的应用程序将扮演了这个服务角色。你只需要简单的引用ZeroMQ程序库,可以使用NuGet安装,然后你就可以愉快的在应用程序之间发送消息了。但是ZeroMQ仅提供非持久性的队列,也就是说如果down机,数据将会丢失。其中,Twitter的Storm中使用ZeroMQ作为数据流的传输。

ActiveMQ

是Apache下的一个子项目。 类似于ZeroMQ,它能够以代理人和点对点的技术实现队列。同时类似于RabbitMQ,它少量代码就可以高效地实现高级应用场景。RabbitMQ、ZeroMQ、ActiveMQ均支持常用的多种语言客户端 C++、Java、.Net,、Python、 Php、 Ruby等。

Jafka/Kafka

Kafka是Apache下的一个子项目,是一个高性能跨语言分布式Publish/Subscribe消息队列系统,而Jafka是在Kafka之上孵化而来的,即Kafka的一个升级版。具有以下特性:快速持久化,可以在O(1)的系统开销下进行消息持久化;高吞吐,在一台普通的服务器上既可以达到10W/s的吞吐速率;完全的分布式系统,Broker、Producer、Consumer都原生自动支持分布式,自动实现复杂均衡;支持Hadoop数据并行加载,对于像Hadoop的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka通过Hadoop的并行加载机制来统一了在线和离线的消息处理,这一点也是本课题所研究系统所看重的。Apache Kafka相对于ActiveMQ是一个非常轻量级的消息系统,除了性能非常好之外,还是一个工作良好的分布式系统。

其他一些队列列表HornetQ、Apache Qpid、Sparrow、Starling、Kestrel、Beanstalkd、Amazon SQS就不再一一分析。

 4.线程调用

 

当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?
分几种情况:
     1.其他方法前是否加了synchronized关键字,如果没加,则能。
     2.如果这个方法内部调用了wait,则可以进入其他synchronized方法。
     3.如果其他个方法都加了synchronized关键字,并且内部没有调用wait,则不能。
     4.如果其他方法是static,它用的同步锁是当前类的字节码,与非静态的方法不能同步,因为非静态的方法用的是this

 

5.REDIS

 

1.Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。

2.Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。

3.Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。

下面是我的一个实际项目的情况,大概情况是这样的:一个Master,4个Slave,没有Sharding机制,仅是读写分离,Master负责写入操作和AOF日志备份,AOF文件大概5G,Slave负责读操作,当Master调用BGREWRITEAOF时,Master和Slave负载会突然陡增,Master的写入请求基本上都不响应了,持续了大概5分钟,Slave的读请求过也半无法及时响应,上面的情况本来不会也不应该发生的,是因为以前Master的这个机器是Slave,在上面有一个shell定时任务在每天的上午10点调用BGREWRITEAOF重写AOF文件,后来由于Master机器down了,就把备份的这个Slave切成Master了,但是这个定时任务忘记删除了,就导致了上面悲剧情况的发生,原因还是找了几天才找到的。

no-appendfsync-on-rewrite的配置设为yes可以缓解这个问题,设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入。最好是不开启Master的AOF备份功能。

4.Redis主从复制的性能问题,第一次Slave向Master同步的实现是:Slave向Master发出同步请求,Master先dump出rdb文件,然后将rdb文件全量传输给slave,然后Master把缓存的命令转发给Slave,初次同步完成。第二次以及以后的同步实现是:Master将变量的快照直接实时依次发送给各个Slave。不管什么原因导致Slave和Master断开重连都会重复以上过程。Redis的主从复制是建立在内存快照的持久化基础上,只要有Slave就一定会有内存快照发生。虽然Redis宣称主从复制无阻塞,但由于磁盘io的限制,如果Master快照文件比较大,那么dump会耗费比较长的时间,这个过程中Master可能无法响应请求,也就是说服务会中断,对于关键服务,这个后果也是很可怕的。

以上1.2.3.4根本问题的原因都离不开系统io瓶颈问题,也就是硬盘读写速度不够快,主进程 fsync()/write() 操作被阻塞。

5.单点故障问题,由于目前Redis的主从复制还不够成熟,所以存在明显的单点故障问题,这个目前只能自己做方案解决,如:主动复制,Proxy实现Slave对Master的替换等,这个也是Redis作者目前比较优先的任务之一,作者的解决方案思路简单优雅,详情可见 Redis Sentinel design draft http://redis.io/topics/sentinel-spec

 

总结:

1.Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化。

2.如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。

3.为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内。

4.尽量避免在压力较大的主库上增加从库

 

5.为了Master的稳定性,主从复制不要用图状结构,用单向链表结构更稳定,即主从关系为:Master<--Slave1<--Slave2<--Slave3.......,这样的结构也方便解决单点故障问题,实现Slave对Master的替换,也即,如果Master挂了,可以立马启用Slave1做Master,其他不变。

 

=====================================

1.  在执行速度方面的比较:StringBuilder >  StringBuffer   
2.  StringBuffer与StringBuilder,他们是字符串变量,是可改变的对象,每当我们用它们对字符串做操作时,实际上是在一个对象上操作的,不像String一样创建一些对象进行操作,所以速度就快了。
3.  StringBuilder:线程非安全的
  StringBuffer:线程安全的
    当我们在字符串缓冲去被多个线程使用是,JVM不能保证StringBuilder的操作是安全的,虽然他的速度最快,但是可以保证StringBuffer是可以正确操作的。当然大多数情况下就是我们是在单线程下进行的操作,所以大多数情况下是建议用StringBuilder而不用StringBuffer的,就是速度的原因。

对于三者使用的总结:1.如果要操作少量的数据用 = String
                    2.单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
                    3.多线程操作字符串缓冲区 下操作大量数据 = StringBuffer

 StringBuffer属于线程安全,相对为重量级 StringBuilder属于非线程安全,相对为轻量级 线程安全的概念: 网络编程中许多线程可能会同时运行一段代码。当每次运行结果和单独线程运行的结果是一样的,叫做线程安全。 为了达到线程安全的目的在一定程度上会降低程序的性能。所以在单线程中,StringBuilder的性能要比StringBuffer高。多线程

 

=============================

虽然jvm可以通过GC自动回收无用的内存,但是代码不好的话仍然存在内存溢出的风险。

最近在网上搜集了一些资料,现整理如下:

 ——————————————————————————————————————————

 

一、为什么要了解内存泄露和内存溢出?

 

1、内存泄露一般是代码设计存在缺陷导致的,通过了解内存泄露的场景,可以避免不必要的内存溢出和提高自己的代码编写水平;

 

2、通过了解内存溢出的几种常见情况,可以在出现内存溢出的时候快速的定位问题的位置,缩短解决故障的时间。

 

 

 二、基本概念

 

理解这两个概念非常重要。

 

内存泄露:指程序中动态分配内存给一些临时对象,但是对象不会被GC所回收,它始终占用内存。即被分配的对象可达但已无用

 

内存溢出:指程序运行过程中无法申请到足够的内存而导致的一种错误。内存溢出通常发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况。

 

从定义上可以看出内存泄露是内存溢出的一种诱因,不是唯一因素。

 

 

三、内存泄露的几种场景:

 

1、长生命周期的对象持有短生命周期对象的引用

 

            这是内存泄露最常见的场景,也是代码设计中经常出现的问题。

            例如:在全局静态map中缓存局部变量,且没有清空操作,随着时间的推移,这个map会越来越大,造成内存泄露。

 

2、修改hashset中对象的参数值,且参数是计算哈希值的字段

 

             当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段,否则对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中删除当前对象,造成内存泄露。

 

3、机器的连接数和关闭时间设置

 

            长时间开启非常耗费资源的连接,也会造成内存泄露。

 

 

 

 四、内存溢出的几种情况:

 

1、堆内存溢出outOfMemoryError:java heap space

       在jvm规范中,堆中的内存是用来生成对象实例和数组的。

       如果细分,堆内存还可以分为年轻代和年老代,年轻代包括一个eden区和两个survivor区。

       当生成新对象时,内存的申请过程如下:

          a、jvm先尝试在eden区分配新建对象所需的内存;

          b、如果内存大小足够,申请结束,否则下一步;

          c、jvm启动youngGC,试图将eden区中不活跃的对象释放掉,释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区;

          d、Survivor区被用来作为Eden及old的中间交换区域,当OLD区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区;

          e、 当OLD区空间不够时,JVM会在OLD区进行full GC;

          f、full GC后,若Survivor及OLD区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现”out of memory错误”:

                                   outOfMemoryError:java heap space

 

代码举例:

 

Java代码  收藏代码
  1. <span style="font-size: 11px;">/** 
  2. * 堆内存溢出 
  3. * 
  4. * jvm参数:-Xms5m -Xmx5m -Xmn2m -XX:NewSize=1m 
  5. * 
  6. */  
  7. public class MemoryLeak {  
  8.      
  9.     private String[] s = new String[1000];  
  10.    
  11.     public static void main(String[] args) throws InterruptedException {  
  12.         Map<String,Object> m =new HashMap<String,Object>();  
  13.         int i =0;  
  14.         int j=10000;  
  15.         while(true){  
  16.             for(;i<j;i++){  
  17.                 MemoryLeak memoryLeak = new MemoryLeak();  
  18.                 m.put(String.valueOf(i), memoryLeak);  
  19.             }  
  20.         }  
  21.     }  
  22. }</span>  

 

 

           

2、方法区内存溢出outOfMemoryError:permgem space

       在jvm规范中,方法区主要存放的是类信息、常量、静态变量等。

       所以如果程序加载的类过多,或者使用反射、gclib等这种动态代理生成类的技术,就可能导致该区发生内存溢出,一般该区发生内存溢出时的错误信息为:

             outOfMemoryError:permgem space

 

代码举例:

 

Java代码  收藏代码
  1. <span style="font-size: 11px;">jvm参数:-XX:PermSize=2m -XX:MaxPermSize=2m  
  2.   
  3. 将方法区的大小设置很低即可,在启动加载类库时就会出现内存不足的情况</span>  

 

 

 

3、线程栈溢出java.lang.StackOverflowError

       线程栈时线程独有的一块内存结构,所以线程栈发生问题必定是某个线程运行时产生的错误。

       一般线程栈溢出是由于递归太深或方法调用层级过多导致的。

       发生栈溢出的错误信息为:

              java.lang.StackOverflowError

 

代码举例:

 

Java代码  收藏代码
  1. <span style="font-size: 11px;">/** 
  2. * 线程操作栈溢出 
  3. * 
  4. * 参数:-Xms5m -Xmx5m -Xmn2m -XX:NewSize=1m -Xss64k 
  5. * 
  6. */  
  7. public class StackOverflowTest {  
  8.      
  9.     public static void main(String[] args) {  
  10.         int i =0;  
  11.         digui(i);  
  12.     }  
  13.      
  14.     private static void digui(int i){  
  15.         System.out.println(i++);  
  16.         String[] s = new String[50];  
  17.         digui(i);  
  18.     }  
  19.   
  20. }  
  21. </span>  

 

五、为了避免内存泄露,在编写代码的过程中可以参考下面的建议:

 

1、尽早释放无用对象的引用

 

2、使用字符串处理,避免使用String,应大量使用StringBuffer,每一个String对象都得独立占用内存一块区域

 

3、尽量少用静态变量,因为静态变量存放在永久代(方法区),永久代基本不参与垃圾回收

 

4、避免在循环中创建对象

 

 

5、开启大型文件或从数据库一次拿了太多的数据很容易造成内存溢出,所以在这些地方要大概计算一下数据量的最大值是多少,并且设定所需最小及最大的内存空间值。 

=======================================

 

<!--核心线程池-->
<beanid="taskExecutor" name="taskExecutor_passive"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <propertyname="corePoolSize" value="40"/>
    <propertyname="maxPoolSize" value="100"/>
    <propertyname="queueCapacity" value="3072"/>
    <propertyname="rejectedExecutionHandler">
        <beanclass="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" /><!--调用者运行-->
</property>
</bean>

/**
 * 线程池
 */
@Resource(name = "taskExecutor")
private ThreadPoolTaskExecutor cartPropertyDataGetterTaskExecutor;

public ThreadPoolTaskExecutor getTaskExecutor() {
   return cartPropertyDataGetterTaskExecutor;
}

public void setTaskExecutor(ThreadPoolTaskExecutor taskExecutor) {
   this.cartPropertyDataGetterTaskExecutor = taskExecutor;
}

// 命令立减查询
if (result.isActivateKeyt() && currentCart.getKeyt() != null && StringUtil.isNotBlank(currentCart.getKeyt().getKeytStr())) {
   keytFuture = this.getTaskExecutor().submit(new KeytGetter(keytBS, user, siteId, currentCart.getKeyt().getKeytStr(), currentCart, commerceItemList, priceInfo, shippingGroups));
}

if (blueCouponsFuture != null) {
   final BlueCouponSummary blueCouponSummary = buildBlueCouponSummary(blueCouponsFuture.get());
   result.setBlueCouponsAmount(blueCouponSummary.getUsedAmount()); // 蓝券的使用金额
result.setBlueCouponsNum(blueCouponSummary.getUsedNum()); // 蓝券的使用张数
result.setBlueCoupons(blueCouponSummary.getList());
}
if (shopCouponsFuture != null) {
   final ShopCouponSummary shopCouponSummary = buildShopCouponSummary(shopCouponsFuture.get());
   result.setShopCouponsAmount(shopCouponSummary.getUsedAmount()); // 店铺券使用金额
result.setShopCouponsNum(shopCouponSummary.getUsedNum()); // 店铺券使用张数
result.setQueryShopCoupons(shopCouponSummary.getList());
}
if (redCouponsFuture != null) {
   final RedCouponSummary redCouponSummary = buildRedCouponSummary(redCouponsFuture.get());
   result.setRedCouponsAmount(redCouponSummary.getUsedAmount()); // 红券使用的金额
result.setRedCouponsNum(redCouponSummary.getUsedNum()); // 红券使用的张数
result.setRedCoupons(redCouponSummary.getList());
}
if (prepaidCardFuture != null) {
   final PrepaidCardSummary prepaidCardSummary = buildPrepaidCardSummary(prepaidCardFuture.get());
   result.setPrepaidCardsAmount(prepaidCardSummary.getUsedAmount()); // 预付卡使用的金额
result.setPrepaidCardsNum(prepaidCardSummary.getUsedNum()); // 预付卡使用的张数
result.setPrepaidCards(prepaidCardSummary.getList());
}
VirtualAccountResult virtualAccountResult = null;
if (virtualAccountResultFuture != null) {
   virtualAccountResult = virtualAccountResultFuture.get();
   result.setVirtualAccountResult(virtualAccountResult);
}
if ((containsHaiWaiGou || !shouldQueryVirtualAccount(siteId, commerceItemList)) && virtualAccountResult != null) {
   virtualAccountResult.setPayAmount(DOUBLE_ZERO); // 可用金额为0
}

if (blueCouponsFuture != null) {
   final BlueCouponSummary blueCouponSummary = buildBlueCouponSummary(blueCouponsFuture.get());
   result.setBlueCouponsAmount(blueCouponSummary.getUsedAmount()); // 蓝券的使用金额
result.setBlueCouponsNum(blueCouponSummary.getUsedNum()); // 蓝券的使用张数
result.setBlueCoupons(blueCouponSummary.getList());
}
if (shopCouponsFuture != null) {
   final ShopCouponSummary shopCouponSummary = buildShopCouponSummary(shopCouponsFuture.get());
   result.setShopCouponsAmount(shopCouponSummary.getUsedAmount()); // 店铺券使用金额
result.setShopCouponsNum(shopCouponSummary.getUsedNum()); // 店铺券使用张数
result.setQueryShopCoupons(shopCouponSummary.getList());
}
if (redCouponsFuture != null) {
   final RedCouponSummary redCouponSummary = buildRedCouponSummary(redCouponsFuture.get());
   result.setRedCouponsAmount(redCouponSummary.getUsedAmount()); // 红券使用的金额
result.setRedCouponsNum(redCouponSummary.getUsedNum()); // 红券使用的张数
result.setRedCoupons(redCouponSummary.getList());
}
if (prepaidCardFuture != null) {
   final PrepaidCardSummary prepaidCardSummary = buildPrepaidCardSummary(prepaidCardFuture.get());
   result.setPrepaidCardsAmount(prepaidCardSummary.getUsedAmount()); // 预付卡使用的金额
result.setPrepaidCardsNum(prepaidCardSummary.getUsedNum()); // 预付卡使用的张数
result.setPrepaidCards(prepaidCardSummary.getList());
}
VirtualAccountResult virtualAccountResult = null;
if (virtualAccountResultFuture != null) {
   virtualAccountResult = virtualAccountResultFuture.get();
   result.setVirtualAccountResult(virtualAccountResult);
}
if ((containsHaiWaiGou || !shouldQueryVirtualAccount(siteId, commerceItemList)) && virtualAccountResult != null) {
   virtualAccountResult.setPayAmount(DOUBLE_ZERO); // 可用金额为0
}
if (storePointFuture != null) {
   result.setStorePoint(storePointFuture.get());
}
if (gomedoFuture != null) {
   result.setGomedo(gomedoFuture.get());
}
if (keytFuture != null) {
   result.setKeyt(keytFuture.get());
}

 

package com.gome.pangu.trading.cart.business.taskflow.listcart.task;

import java.util.List;
import java.util.concurrent.Callable;

import com.gome.framework.bleach.Bleacher;
import com.gome.framework.logging.Logger;
import com.gome.pangu.pricing.client.dto.result.PriceInfoResultRDTO;
import com.gome.pangu.trading.bo.Cart;
import com.gome.pangu.trading.bo.CommerceItem;
import com.gome.pangu.trading.bo.ShippingGroup;
import com.gome.pangu.trading.bo.User;
import com.gome.pangu.trading.keyt.business.KeytBS;
import com.gome.pangu.trading.keyt.client.dto.result.Keyt;

/**
 * 命令立减查询服务Callable实现类
 * Created by huangwenfeng on 2016/9/28.
 */
public class KeytGetter implements Callable<Keyt> {

   private static final Logger LOGGER = Bleacher.getLogger(KeytGetter.class);
   private final KeytBS keytBS;
   private final User user;
   private final String siteId;
   private final String keytStr;
   private final Cart currentCart;
   private final List<CommerceItem> commerceItemList;
   private final PriceInfoResultRDTO priceInfoResultRDTO;
   private final List<ShippingGroup> shippingGroups;

   public KeytGetter(KeytBS keytBS, User user, String siteId, String keytStr, Cart currentCart, List<CommerceItem> commerceItemList, PriceInfoResultRDTO priceInfoResultRDTO,
         List<ShippingGroup> shippingGroups) {
      this.keytBS = keytBS;
      this.user = user;
      this.siteId = siteId;
      this.keytStr = keytStr;
      this.currentCart = currentCart;
      this.commerceItemList = commerceItemList;
      this.priceInfoResultRDTO = priceInfoResultRDTO;
      this.shippingGroups = shippingGroups;
   }

   @Override
public Keyt call() throws Exception {

      try {
         Keyt keyt = keytBS.queryKeyt(user, siteId, keytStr, currentCart, commerceItemList, priceInfoResultRDTO, shippingGroups);
         return keyt;
      } catch (Exception e) {
         LOGGER.error("调用命令立减查询服务失败,错误信息:", e);
         LOGGER.error("调用命令立减查询服务失败,用户ID:{},站点ID:{},口令串:{},Cart:{},商品列表:{},价格信息:{},配送单:{}", user.getUserId(), siteId, keytStr, currentCart, commerceItemList, priceInfoResultRDTO, shippingGroups);
         return null;
      }
   }
}


/**
 * 口令立减
 * Created by huangwenfeng on 2016/9/26.
 */
public class Keyt extends BaseModel {

   private static final long serialVersionUID = 4824030816608236547L;

   private String keytStr; // 命令串
private double remainingAmount; // 剩余支付金额(OMS备用)
private double amount = 0; // 命令立减兑换到的金额
private int status = 0; // 向前端返回状态错误码 0-初始状态(没有用命令立减) 1-正常(可用命令立减)
public double getAmount() {
      return amount;
   }

   public void setAmount(double amount) {
      this.amount = amount;
   }

   public int getStatus() {
      return status;
   }

   public void setStatus(int status) {
      this.status = status;
   }

   public String getKeytStr() {
      return keytStr;
   }

   public void setKeytStr(String keytStr) {
      this.keytStr = keytStr;
   }

   public double getRemainingAmount() {
      return remainingAmount;
   }

   public void setRemainingAmount(double remainingAmount) {
      this.remainingAmount = remainingAmount;
   }
}

 ----------------------------------------------------------------------------------------

为什么覆写equals的时候一定要覆写hashCode?

Java中的equals方法和hashCode方法是Object中的,所以每个对象都是有这两个方法的,有时候我们需要实现特定需求,可能要重写这两个方法,今天就来介绍一些这两个方法的作用。

 

equals()和hashCode()方法是用来在同一类中做比较用的,尤其是在容器里如set存放同一类对象时用来判断放入的对象是否重复。

这里我们首先要明白一个问题:

          equals()相等的两个对象,hashcode()一定相等,equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。换句话说,equals()方法不相等的两个对象,hashCode()有可能相等。(我的理解是由于哈希码在生成的时候产生冲突造成的)

       在这里hashCode就好比字典里每个字的索引,equals()好比比较的是字典里同一个字下的不同词语。就好像在字典里查“自”这个字下的两个词语“自己”、“自发”,如果用equals()判断查询的词语相等那么就是同一个词语,比如equals()比较的两个词语都是“自己”,那么此时hashCode()方法得到的值也肯定相等;如果用equals()方法比较的是“自己”和“自发”这两个词语,那么得到结果是不想等,但是这两个词都属于“自”这个字下的词语所以在查索引时相同,即:hashCode()相同。如果用equals()比较的是“自己”和“他们”这两个词语的话那么得到的结果也是不同的,此时hashCode() 得到也是不同的。


     反过来:hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。在object类中,hashcode()方法是本地方法,返回的是对象的地址值,而object类中的equals()方法比较的也是两个对象的地址值,如果equals()相等,说明两个对象地址值也相等,当然hashcode() 也就相等了;

 

同时hash算法对于查找元素提供了很高的效率

如果想查找一个集合中是否包含有某个对象,大概的程序代码怎样写呢?
你通常是逐一取出每个元素与要查找的对象进行比较,当发现某个元素与要查找的对象进行equals方法比较的结果相等时,则停止继续查找并返回肯定的信息,否则,返回否定的信息,如果一个集合中有很多个元素,比如有一万个元素,并且没有包含要查找的对象时,则意味着你的程序需要从集合中取出一万个元素进行逐一比较才能得到结论。

有人发明了一种哈希算法来提高从集合中查找元素的效率,这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码,可以将哈希码分组(使用不同的hash函数来计算的),每组分别对应某个存储区域,根据一个对象的哈希吗就可以确定该对象应该存储在哪个区域HashSet就是采用哈希算法存取对象的集合,它内部采用对某个数字n进行取余(这种的hash函数是最简单的)的方式对哈希码进行分组和划分对象的存储区域;Object类中定义了一个hashCode()方法来返回每个Java对象的哈希码,当从HashSet集合中查找某个对象时,Java系统首先调用对象的hashCode()方法获得该对象的哈希码表,然后根据哈希吗找到相应的存储区域,最后取得该存储区域内的每个元素与该对象进行equals方法比较;这样就不用遍历集合中的所有元素就可以得到结论,可见,HashSet集合具有很好的对象检索性能,但是,HashSet集合存储对象的效率相对要低些,因为向HashSet集合中添加一个对象时,要先计算出对象的哈希码和根据这个哈希码确定对象在集合中的存放位置为了保证一个类的实例对象能在HashSet正常存储,要求这个类的两个实例对象用equals()方法比较的结果相等时,他们的哈希码也必须相等;也就是说,如果obj1.equals(obj2)的结果为true,那么以下表达式的结果也要为true:
obj1.hashCode() == obj2.hashCode()
换句话说:当我们重写一个对象的equals方法,就必须重写他的hashCode方法,不过不重写他的hashCode方法的话,Object对象中的hashCode方法始终返回的是一个对象的hash地址,而这个地址是永远不相等的。所以这时候即使是重写了equals方法,也不会有特定的效果的,因为hashCode方法如果都不想等的话,就不会调用equals方法进行比较了,所以没有意义了。

 

如果一个类的hashCode()方法没有遵循上述要求,那么,当这个类的两个实例对象用equals()方法比较的结果相等时,他们本来应该无法被同时存储进set集合中,但是,如果将他们存储进HashSet集合中时,由于他们的hashCode()方法的返回值不同(Object中的hashCode方法返回值是永远不同的),第二个对象首先按照哈希码计算可能被放进与第一个对象不同的区域中,这样,它就不可能与第一个对象进行equals方法比较了,也就可能被存储进HashSet集合中了,Object类中的hashCode()方法不能满足对象被存入到HashSet中的要求,因为它的返回值是通过对象的内存地址推算出来的,同一个对象在程序运行期间的任何时候返回的哈希值都是始终不变的,所以,只要是两个不同的实例对象,即使他们的equals方法比较结果相等,他们默认的hashCode方法的返回值是不同的。

--------------------------------------------------------------------------

mysql更新死锁问题

问题现象

同一时间点,一个商户下多个模板(模板id不同)同时更新;
券模板操作流水更新时发生死锁,报 Deadlock found when trying to get lock; try restarting transaction 错误

相关业务表信息

  • 表:log_xx
  • 索引信息:log_no:分库分表全局唯一uk; merchant_id: 普通索引
    image

业务处理逻辑

  • 1、启动本地事务
  • 2、按照logNo + merchantPid加锁查询流水表;
    • 伪代码SQL:select * from log_xx where log_no='#logNo#' and merchant_id='#merchantPid#'
  • 3、业务处理(含流水表及本地其他表插入、更新等。。。)
  • 4、业务处理完毕,按照logNo + merchantPid更新流水表;
    • 伪代码SQL:update log_xx set 字段X=value_Y where log_no='#logNo#' and merchant_id='#merchantPid#'
  • 5、事务提交

问题原因

MySQL使用了index merge,update语句中where条件走多索引且并发高时有概率出现死锁

  • update时,如果where条件里面涉及多个字段,区分度都比较高且字段都分别建了索引的话,mysql会多个索引各走一遍,然后结果取个交集;
  • 单条记录更新不会引发问题; 多条记录并发更新时,如果索引行数有重叠,因加锁顺序可能不同,互相等待可能会导致死锁,为什么加锁顺序会不同呢?我们的sql中where条件的顺序是一定的,那么加锁顺序也应该一定,为什么会有加锁顺序不同情况。情况是这样的,因为我们使用的是两个单值索引,where条件中是复合条件,那么mysql会使用index merge进行优化,优化过程是mysql会先用索引1进行扫表,在用索引2进行扫表,然后求交集形成一个合并索引。这个使用索引扫表的过程和我们本身的sql使用索引的顺序可能存在互斥,所以造成了死锁。
  • 更多问题说明及解决方案请参见https://bugs.mysql.com/bug.php?id=77209

解决方案

  • 方案一:流水表中log_no是全局唯一的,更新表时只使用log_no作为查询条件; 如果期望校验merchantId的合法性,查出单条记录后放业务中作;
  • 方案二:根据以上文档说明,使用update force index理论上也能解决问题; 针对自身业务,相比而言上个方案更优,暂不采用该方案;

问题相关记录及截图

  • 待更新的两条log,商户id都是2088702993114853,但log_no不同;
  • 第一个线程 traceId=0ad5798414695108358467441_0_b8043a84_7_1_4_20
    • 2016-07-26 13:27:16,871
      select * from log_85 where log_no='894185' and merchant_id=' 114853'
    • 2016-07-26 13:27:17,269
      update log_85 set 字段xx=value xx where log_no='894185' and merchant_id='114853'
  • 第二个线程 traceId=0ad5798414695108358467441_0_b8043a84_7_1_12_20
    • 2016-07-26 13:27:16,868
      select * from log_85 where log_no=893985' and merchant_id='114853'
    • 2016-07-26 13:27:17,179
      update 85 set 字段xx=value xx where log_no='893985' and merchant_id='114853'
  • 更新log表使用了intersect
  • 错误日志堆栈
    --- Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction; nested exception is com.ibatis.common.jdbc.exception.NestedSQLException:
    imageimage

 

 

--------------------------------------------------------------------- 

spring里面事务的传播属性和事务隔离级别

一、Propagation (事务的传播属性)

Propagation :  key属性确定代理应该给哪个方法增加事务行为。这样的属性最重要的部份是传播行为。有以下选项可供使用:PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。

PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。

PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。

PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。

PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。

1: PROPAGATION_REQUIRED

加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务

比如说,ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED, 那么由于执行ServiceA.methodA的时候,

ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA

的事务内部,就不再起新的事务。而假如ServiceA.methodA运行的时候发现自己没有在事务中,他就会为自己分配一个事务。

这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被

提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚

2: PROPAGATION_SUPPORTS

如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行

3: PROPAGATION_MANDATORY

必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常

4: PROPAGATION_REQUIRES_NEW

这个就比较绕口了。  比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW,

那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,

他才继续执行。他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务,那么就是存在

两个不同的事务。如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚,

如果他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。

5: PROPAGATION_NOT_SUPPORTED

当前不支持事务。比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED ,而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED ,

那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,而他以非事务的状态运行完,再继续ServiceA.methodA的事务。

6: PROPAGATION_NEVER

不能在事务中运行。假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED,  而ServiceB.methodB的事务级别是PROPAGATION_NEVER ,

那么ServiceB.methodB就要抛出异常了。

7: PROPAGATION_NESTED

理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,

而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。

而Nested事务的好处是他有一个savepoint。

*****************************************

ServiceA {

/**

* 事务属性配置为 PROPAGATION_REQUIRED

*/

void methodA() {

try {

//savepoint

ServiceB.methodB(); //PROPAGATION_NESTED 级别

} catch (SomeException) {

// 执行其他业务, 如 ServiceC.methodC();

}

}

}

********************************************

也就是说ServiceB.methodB失败回滚,那么ServiceA.methodA也会回滚到savepoint点上,ServiceA.methodA可以选择另外一个分支,比如

ServiceC.methodC,继续执行,来尝试完成自己的事务。

但是这个事务并没有在EJB标准中定义。

二、Isolation Level(事务隔离等级):

1、Serializable:最严格的级别,事务串行执行,资源消耗最大;

2、REPEATABLE  READ:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。

3、READ  COMMITTED:大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。

4、Read  Uncommitted:保证了读取过程中不会读取到非法数据。隔离级别在于处理多事务的并发问题。

我们知道并行可以提高数据库的吞吐量和效率,但是并不是所有的并发事务都可以并发运行,这需要查看数据库教材的可串行化条件判断了。

这里就不阐述。

我们首先说并发中可能发生的3中不讨人喜欢的事情

1: Dirty  reads--读脏数据。也就是说,比如事务A的未提交(还依然缓存)的数据被事务B读走,如果事务A失败回滚,会导致事务B所读取的的数据是错误的。

2: non-repeatable  reads--数据不可重复读。比如事务A中两处读取数据-total-的值。在第一读的时候,total是100,然后事务B就把total的数据改成 200,事务A再读一次,结果就发现,total竟然就变成200了,造成事务A数据混乱。

3: phantom reads--幻象读数据,这个和non-repeatable  reads相似,也是同一个事务中多次读不一致的问题。但是non-repeatable  reads的不一致是因为他所要取的数据集被改变了(比如total的数据),但是phantom  reads所要读的数据的不一致却不是他所要读的数据集改变,而是他的条件数据集改变。比如Select account.id where  account.name="ppgogo*",第一次读去了6个符合条件的id,第二次读取的时候,由于事务b把一个帐号的名字由"dd"改成"ppgogo1",结果取出来了7个数据。

 

  Dirty reads non-repeatable reads phantom reads
Serializable 不会 不会 不会
REPEATABLE READ 不会 不会
READ COMMITTED 不会
Read Uncommitted

 

 

三、readOnly

事务属性中的readOnly标志表示对应的事务应该被最优化为只读事务。

这是一个最优化提示。在一些情况下,一些事务策略能够起到显著的最优化效果,例如在使用Object/Relational映射工具(如:Hibernate或TopLink)时避免dirty  checking(试图“刷新”)。

四、Timeout

在事务属性中还有定义“timeout”值的选项,指定事务超时为几秒。在JTA中,这将被简单地传递到J2EE服务器的事务协调程序,并据此得到相应的解释

 

 

总结:

  1. <aop:config proxy-target-class="true">  
  2.     <aop:advisor pointcut="execution(* com.company..*Manager.*(..))" 
  3.         advice-ref="txAdvice" />  
  4. </aop:config>  
  5.  
  6. <tx:advice id="txAdvice">  
  7.     <tx:attributes>  
  8.         <tx:method name="save*"isolation="DEFAULT/READ_COMMITTED/READ_UNCOMMITTED/REPEATABLE_READ/SERIALIZABLE]"/>  
  9.         <tx:method name="update*" <tx:method name="update*"propagation="MANDATORY/NESTED/NEVER/NOT_SUPPORTED/REQUIRED/REQUIRES_NEW/SUPPORTS">/>  
  10.         <tx:method name="delete*" />  
  11.         <tx:method name="find*" read-only="true" />  
  12.     </tx:attributes>  
  13. </tx:advice> 

 

 

   isolation设定事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据。 定义的5个不同的事务隔离级别: DEFAULT:默认的隔离级别,使用数据库默认的事务隔离级别 READ_COMMITTED:保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。 READ_UNCOMMITTED:这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。 REPEATABLE_READ:这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免不可重复读。 SERIALIZABLE:这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。 
propagation定义了7个事务传播行为 REQUIRED: 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。 SUPPORTS: 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,SUPPORTS与不使用事务有少许不同。 REQUIRES_NEW 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。 NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。 NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常 NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。

嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。 

---------------------------------------------------------------------

spring里面事务的传播属性和事务隔离级别

一、Propagation (事务的传播属性)

Propagation :  key属性确定代理应该给哪个方法增加事务行为。这样的属性最重要的部份是传播行为。有以下选项可供使用:PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。

1: PROPAGATION_REQUIRED
加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务
比如说,ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED, 那么由于执行ServiceA.methodA的时候,
ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA
的事务内部,就不再起新的事务。而假如ServiceA.methodA运行的时候发现自己没有在事务中,他就会为自己分配一个事务。
这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被
提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚

2: PROPAGATION_SUPPORTS
如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行


3: PROPAGATION_MANDATORY
必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常

4: PROPAGATION_REQUIRES_NEW
这个就比较绕口了。 比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW,
那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,
他才继续执行。他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务,那么就是存在
两个不同的事务。如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚,
如果他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。

5: PROPAGATION_NOT_SUPPORTED
当前不支持事务。比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED ,而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED ,
那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,而他以非事务的状态运行完,再继续ServiceA.methodA的事务。

6: PROPAGATION_NEVER
不能在事务中运行。假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED, 而ServiceB.methodB的事务级别是PROPAGATION_NEVER ,
那么ServiceB.methodB就要抛出异常了。

7: PROPAGATION_NESTED
理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,
而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。
而Nested事务的好处是他有一个savepoint。
*****************************************
ServiceA {

/**
* 事务属性配置为 PROPAGATION_REQUIRED
*/
void methodA() {
try {
//savepoint
ServiceB.methodB(); //PROPAGATION_NESTED 级别
} catch (SomeException) {
// 执行其他业务, 如 ServiceC.methodC();
}
}

}
********************************************
也就是说ServiceB.methodB失败回滚,那么ServiceA.methodA也会回滚到savepoint点上,ServiceA.methodA可以选择另外一个分支,比如
ServiceC.methodC,继续执行,来尝试完成自己的事务。
但是这个事务并没有在EJB标准中定义。

spring事务的隔离级别
 1. ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
      另外四个与JDBC的隔离级别相对应
 2. ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。
      这种隔离级别会产生脏读,不可重复读和幻像读。
 3. ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据
 4. ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
      它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
 5. ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。
      除了防止脏读,不可重复读外,还避免了幻像读。

什么是脏数据,脏读,不可重复读,幻觉读?
 脏读: 指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,
     另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据, 那么另外一
     个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。
    
 不可重复读: 指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。
             那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据
             可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
            
 幻觉读: 指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及
         到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,
         以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。

----------------------------------------------------------------------------

 Read Committed(读取提交内容)

       这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
Repeatable Read(可重读)

       这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics