一、定义
在多线程情况下,如果一个线程对拥有某个资源的锁,那么这个线程就可以运行资源相关的代码。而其他线程就只能等待其执行完毕后,才能继续争夺资源锁,从而运行相关代码。
二、场景
车票售卖系统,当前剩余车票1张,此时有A、B、C、D四个用户同时来购票,系统开启了四个线程来执行业务操作,其中:
A线程刚执行了step1;
CPU将执行权切换到B线程;
B线程顺利的执行了step1和step2,此时剩余票数为0;
CPU将执行权又切换到A线程,A线程继续执行step2,此时票数为-1,多卖出去一张票!出现异常业务数据。
这种情况就是多线程模式下的数据安全问题。
三、解决方案
在多线程的情况下,如果存在修改共享数据的操作,就要对操作步骤进行加锁,拥有锁的线程才可以执行相关代码。没有锁的线程只能等待其释放锁后,才有资格继续执行代码。
synchronized(this){
step1:判断车票数量;
step2:车票数量-1;}
四、场景复现:
车票售卖系统,当前剩余车票1张,此时有A、B、C、D四个用户同时来购票,系统开启了四个线程来执行业务操作。
A线程最先执行代码块,并进行加锁操作,执行了step1,此时A线程拥有锁;
CPU将执行权切换到B线程,B线程准备执行代码块,发现代码块已经加锁,只能继续等待;
CPU将执行权切换到C线程,发现代码块已经加锁,只能继续等待;
CPU将执行权切换到D线程,发现代码块已经加锁,只能继续等待;
CPU将执行权切换到A线程,自身拥有锁,继续执行step2,票数为0;
后续其他线程再次执行到代码块的时候,因为票数已经为0了,所以就不再继续执行后续操作。这样就不会出现数据异常问题了。
五、Log4j的线程阻塞问题
Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以
控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
- Log4j的日志级别Level 描述:
ALL 各级包括自定义级别
DEBUG 指定细粒度信息事件是最有用的应用程序调试
ERROR 错误事件可能仍然允许应用程序继续运行
FATAL 指定非常严重的错误事件,这可能导致应用程序中止
INFO 指定能够突出在粗粒度级别的应用程序运行情况的信息的消息
OFF 这是最高等级,为了关闭日志记录
TRACE 指定细粒度比DEBUG更低的信息事件
WARN 指定具有潜在危害的情况
级别越低,日志越多,日志量:ALL > DEBUG > INFO > WARN > ERROR > FATAL
六、线程阻塞问题排查流程
/pinter/case/block: TPS无明显变化,CPU压不上去,jvisualvm查看截图如下,存在大量红色监视状态的线程。
1、做线程dump。 使用jstack 线程号 > t.log
2、在dump文件或log 中搜索关键字"BLOCKED"、"TIME_WAITTING",查看每种状态的count数量
3、按照上述关键字搜索,查看跟本系统有关的业务代码堆栈信息
七、解决方案
1、减少代码中没有必要的日志输出
2、如果可以,修改WEB-INF/classes/log4j.popertites配置,提升日志级别,以减少日志;将下图中的INFO改成ERROR后,重启tomcat;
日志第一行意思是:INFO级别日志打印到console控制台,滚动日志
3、如果可以,更换其他的日志组件,如Log4j2、Logback等