​ Spring针对事务的管理是通过动态代理实现的,那么事务要进行传播首先必须要是被代理的方法之间,这是Spring事务传播的前提。比如:如果在同一个service里两个方法:方法A,方法B上都加了Transactional()

并且用方法A直接调用了方法B此时方法B上的注解Transactional并不生效(具体原因会新增文章说明跟动态代理的机制有关)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//示例1
public class ClassA{
//方法A
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
a(){
...
b();//此时b方法上添加的事务注解并不生效
}
//方法B
@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
b(){
...
}
}

事务传播生效示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//示例2
public class ClassA{
@Autowired
private ClassB classB;
//方法A
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
a(){
...
classB.c();//此时c方法上添加的事务注解会生效
}
//方法B
@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
b(){
...
}
}
public class ClassB{
@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
c(){
...
c();
}
}

Spring 事务传播属性对比

属性 说明 对比
PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择,默认选择。 常用的
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。 当业务方法被设置为PROPAGATION_MANDATORY时,它就不能被非事务的业务方法调用。如将ClassB.c() 设置为PROPAGATION_MANDATORY,如果展现层的Action直接调用c()方法,将引发一个异常。正确的情况是:
c()方法必须被另一个带事务的业务方法调用比如示例2。 PROPAGATION_MANDATORY的方法一般都是被其它业务方法间接调用的。
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 当方法被设置为PROPAGATION_NOT_SUPPORTED时,外层业务方法的事务会被挂起,当内部方法运行完成后,外层方法的事务重新运行。如果外层方法没有事务,直接运行,不需要做任何其它的事。
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。 当业务方法被设置为PROPAGATION_NEVER时,它将不能被拥有事务的其它业务方法调用。假设ClassB.c()
()设置为PROPAGATION_NEVER,当Class.a()拥有一个事务时,c()方法将抛出异常。所以PROPAGATION_NEVER方法一般是被直接调用的如示例1。
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 PROPAGATION_NESTED和PROPAGATION_REQUIRES_NEW的区别在于:PROPAGATION_REQUIRES_NEW 启动一个新的、和外层事务无关的“内部”事务。该事务拥有自己的独立隔离级别和锁,不依赖于外部事务,独立地提交和回滚。当内部事务开始执行时,外部事务 将被挂起,内务事务结束时,外部事务才继续执行。将创建一个全新的事务,它和外层事务没有任何关系.而 PROPAGATION_NESTED 将创建一个依赖于外层事务的子事务,当外层事务提交或回滚时,子事务也会连带提交和回滚。

Comment and share

linux篇

资源查看

  1. 查看内存剩余空间

    1
    2
    3
    4
    5
    6
    free -m -s 2 -c 20 -t  # 内存剩余空间 以M为输出,两秒更新一次,更新20次
    # 输出
    total used free shared buff/cache available
    Mem: 62838 35817 7814 13366 19206 13099
    Swap: 0 0 0
    Total: 62838 35817 7814
  2. 查看nginx中生效的conf文件所在目录(docker 需s要进入容器)

    1
    2
    3
    4
    nginx -t 
    # 输出
    nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
    nginx: configuration file /etc/nginx/nginx.conf test is successful
  3. 查看磁盘空间大小

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    df -h # -h: human缩写,以易读的方式显示结果(即带单位:比如M/G,如果不加这个参数,显示的数字以B为单位)
    df -i: 以inode模式来显示磁盘使用情况,显示inode信息而非块使用量。
    df --total:显示所有的信息。

    #/opt/app/todeav/config#df -h
    Filesystem Size Used Avail Use% Mounted on
    /dev/mapper/VolGroup00-LogVol00
    2.0G 711M 1.2G 38% /
    /dev/mapper/vg1-lv2 20G 3.8G 15G 21% /opt/applog
    /dev/mapper/vg1-lv1 20G 13G 5.6G 70% /opt/app

  4. 查看当前目录所占空间大小

    1
    2
    3
    4
    5
    6
    du -sh  # -h 人性化显示 -s 递归整个目录的大小
    du -ah: 显示目录,其下目录和文件占用的磁盘空间大小,带单位。
    du -c: 显示几个目录或文件占用的磁盘空间大小,还要统计它们的总和。
    du -sh: 查看当前目录总共占的容量,而不单独列出各子项占用的容量。
    du -l:在统计目录占用磁盘空间大小时,把硬链接也统计进来。
    du -lh --max-depth=1:查看当前目录下一级子文件和子目录占用的磁盘容量。
  5. 查看当前目录下所有子文件夹排序后的大小

    1
    du -sh `ls` | sort  # 注意不是单引号`
  6. 内存

    1
    2
    free -h
    free -h:显示内存状态。

打包命令

1
tar -cvf etc.tar /etc # 仅打包,不压缩!-c :打包选项 -v :显示打包进度 -f :使用档案文件

docker 篇

  1. 进入容器

    1
    docker exec -it [containerId|serviceName] /bin/bash
  2. 查看运行容器资源占用情况

    1
    docker stats [contatinerId|serviceName]
  3. 查看容器内日志

    1
    docker logs -f -n 100 [contatinerId|serviceName] | grep -n10 'ERROR' #输出容器运行日志,使用grep查找关键字后10行内容
  4. 拷贝容器里的文件到本地

    1
    docker cp contatinerId:/tmp   /home/tmp
  1. dump docker中

Java 常用排查工具

jstat

官网连接:https://docs.oracle.com/javase/8/docs/technotes/tools/#monitor

jstat 工具允许以固定的监控频次输出JVM的各种监控指标,比如使用-gcutil输出GC和内存占用汇总信息,每隔5秒输出一次,输出100次,可以看到Young GC比较频繁:

1
2
3
4
5
6
jstat -gcutil [pid] [毫秒次]  [多少次]

S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 0.00 8.00 0.00 17.91 20.19 0 0.000 0 0.000 0.000
0.00 0.00 8.00 0.00 17.91 20.19 0 0.000 0 0.000 0.000
0.00 0.00 8.00 0.00 17.91 20.19 0 0.000 0 0.000 0.000

其中,S0 表示 Survivor0 区占用百分比,S1 表示 Survivor1 区占用百分比,E 表示 Eden 区占用百分比,O 表示老年代占用百分比,M 表示元数据区占用百分比,YGC 表示年轻代回收次数,YGCT 表示年轻代回收耗时,FGC 表示老年代回收次数,FGCT 表示老年代回收耗时。

jstack

将输出的线程栈导入https://fastthread.io/ 进行可视化分析

电子书

Linux Tools Quick Tutorial

Docker 命令实战

Comment and share

一. 同步结构

提供了比synchronized更加高级的同步结构:countDownLatch、CyclicBarrier、Semaphore等,可以实现更加丰富的多线程操作。

  1. Semaphore:作为资源控制器限制同时进行工作的线程数量,java版本的信号量实现。

    通过Semaphore实现车站调度demo

  2. CountDownLatch:允许一个或者多个线程等待某些操作完成。

  3. CyclicBarrier:一种辅助性的同步结构,语序多个线程等待到达某个屏障。

    CountDownLatch和CyclicBarrier的区别:

    i. CountDownLatch是不可以重置,所以无法重用,而CyclicBarrier则没有这种限制,可以重用。

    ii. CountDownLatch的基本操作组合是countDown/await。调用await的线程阻塞等待countDown足够的次数,不管你是在一个线程还是多个线程里countDown,只要次数足够即可。CountDownLatch操作的是事件。

    iii. CyclicBarrier的基本操作组合,则就是await,当所有的伙伴(parties)都调用了await,才会继续进行任务,并自动进行重置。注意,正常情况下,CyclicBarrier的重置都是自动发生的,如果我们调用reset方法,单还有线程在等待,就会导致等待线程发生干扰,抛出BrokenBarrierException异常。CyclicBarrier侧重点是线程,而不是调用事件, 它的典型应用场景是用来等待并发线程结束。

二. 线程安全容器

java.util.concurrent 包提供的容器(Queue、List、Set)、Map,从命名上可以大概区分为 Concurrent*、CopyOnWrite和 Blocking

Map形式的:

  1. ConcurrentHashMap:jdk8以前使用分段锁,jdk8后采用CAS

  2. ConcunrrentSkipListMap:

  3. ConcurrentSkipListMap:是TreeMap的线程安全版本。

List形式的:

CopyOnWriteArrayList:通过快照实现,适用于读多写少的场景。在对其实例进行修改操作(add/remove等)会新建一个数据并修改,修改完毕之后,再将原来的引用指向新的数组。

Set形式的:

CopyOnWriteArraySet:同上CopyOnWriteArrayList

Queue形式的:

Queue类图

ArrayBlockQueue: 有界队列,需要显示的指定队列大小

LinkedBlockQueue: 被认为无界,其实有

我在介绍 ReentrantLock 的条件变量用法的时候分析过 ArrayBlockingQueue,不知道你有没有注意到,其条件变量与 LinkedBlockingQueue 版本的实现是有区别的。notEmpty、notFull 都是同一个再入锁的条件变量,而 LinkedBlockingQueue 则改进了锁操作的粒度,头、尾操作使用不同的锁,所以在通用场景下,它的吞吐量相对要更好一些。 — 引用

PriorityBlockQueue: 无边界的优先级队列

三. 强大的Executor框架

  1. newCachedThreadPool(),它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列。
  2. newSingleThreadExecutor(),它的特点在于工作线程数目被限制为 1,操作一个无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目。
  3. newSingleThreadScheduledExecutor() 和 newScheduledThreadPool(int corePoolSize),创建的是个 ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程。
  4. newWorkStealingPool(int parallelism),这是一个经常被人忽略的线程池,Java 8 才加入这个创建方法,其内部会构建。

四. 框架图

并发图谱

参考作品

《Java Core 卷一》

JUC常用4大并发工具类 https://mp.weixin.qq.com/s/Ixz8V0oMHyRvCzJ3EsZkFA

Java并发干货 https://mp.weixin.qq.com/s/Sxnf5teW1vehkhBfkV7E-g

Comment and share

  • page 1 of 1
Author's picture

Topsion

Fullstack Developer


Coder


Xi'an China