今天發現自己寫的線上程序出現數據庫不能同步的問題,查看日志已經停止記錄,隨后使用jstack查看線程的運行狀況,發現有個同步線程鎖住了。
以下是jstack -l 637 問題線程的內容。
"schedulerJob-t-291" #314 daemon prio=5 os_prio=0 tid=0x00007f7d64844800 nid=0x3d5 runnable [0x00007f7d3a107000] java.lang.Thread.State: RUNNABLE at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) at java.net.SocketInputStream.read(SocketInputStream.java:171) at java.net.SocketInputStream.read(SocketInputStream.java:141) at com.mysql.cj.core.io.ReadAheadInputStream.fill(ReadAheadInputStream.java:101) at com.mysql.cj.core.io.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:144) at com.mysql.cj.core.io.ReadAheadInputStream.read(ReadAheadInputStream.java:174) - locked <0x00000000f13c2050> (a com.mysql.cj.core.io.ReadAheadInputStream) at java.io.FilterInputStream.read(FilterInputStream.java:133) at com.mysql.cj.core.io.FullReadInputStream.readFully(FullReadInputStream.java:58) at com.mysql.cj.mysqla.io.SimplePacketReader.readHeader(SimplePacketReader.java:60)
..........
at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212) at com.sun.proxy.$Proxy91.saveAll(Unknown Source) at com.chenerzhu.crawler.proxy.pool.service.impl.ProxyIpServiceImpl.saveAll(ProxyIpServiceImpl.java:51) at com.chenerzhu.crawler.proxy.pool.job.scheduler.SyncDbSchedulerJob$1.call(SyncDbSchedulerJob.java:95) - locked <0x00000000f0745c78> (a java.lang.Class for com.chenerzhu.crawler.proxy.pool.job.scheduler.SyncDbSchedulerJob) at com.chenerzhu.crawler.proxy.pool.job.scheduler.SyncDbSchedulerJob$1.call(SyncDbSchedulerJob.java:55) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) Locked ownable synchronizers: - <0x00000000f1cb9350> (a java.util.concurrent.ThreadPoolExecutor$Worker)
查看代碼發現代碼中有這么一段
FutureTask task = new FutureTask(new Callable<ProxyIp>() { @Override public ProxyIp call() { ...... synchronized (SyncDbSchedulerJob.class){ proxyIpService.saveAll(availableIpList); availableIpList.clear(); } ........ } } ........ try { ProxyIp proxyIp = proxyIpFuture.get(10, TimeUnit.MINUTES); if(proxyIp!=null){ proxyIpList.add(proxyIp); } } catch (InterruptedException e) { log.error("Interrupted ", e); } catch (Exception e) { log.error("error:", e); }
FutureTask中的synchronized批量保存數據,而Future獲取使用了超時限制10分鍾,由於數據量過大,同步時間超出10分鍾了,停止了執行,而synchronized還未釋放鎖。導致線程鎖住了。
最后通過減少每一次批量執行的數據到1000條,成功使synchronized代碼塊執行完釋放鎖。
===================================================================
總結下使用synchronized同步鎖釋放的時機。我們知道程序執行進入同步代碼塊中monitorenter代表嘗試獲取鎖,退出代碼塊monitorexit代表釋放鎖。而在程序中,是無法顯式釋放對同步監視器的鎖的,而會在如下4種情況下釋放鎖。
1、當前線程的同步方法、代碼塊執行結束的時候釋放
2、當前線程在同步方法、同步代碼塊中遇到break 、 return 終於該代碼塊或者方法的時候釋放。
3、出現未處理的error或者exception導致異常結束的時候釋放
4、程序執行了 同步對象 wait 方法 ,當前線程暫停,釋放鎖
在以下兩種情況不會釋放鎖。
1、代碼塊中使用了 Thread.sleep() Thread.yield() 這些方法暫停線程的執行,不會釋放。
2、線程執行同步代碼塊時,其他線程調用 suspend 方法將該線程掛起,該線程不會釋放鎖 ,所以我們應該避免使用 suspend 和 resume 來控制線程 。