今天發現本身寫的線上程序出現數據庫不能同步的問題,查看日誌已經中止記錄,隨後使用jstack查看線程的運行情況,發現有個同步線程鎖住了。java
如下是jstack -l 637 問題線程的內容。mysql
"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)
查看代碼發現代碼中有這麼一段spring
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還未釋放鎖。致使線程鎖住了。sql
最後經過減小每一次批量執行的數據到1000條,成功使synchronized代碼塊執行完釋放鎖。數據庫
===================================================================bash
總結下使用synchronized同步鎖釋放的時機。咱們知道程序執行進入同步代碼塊中monitorenter表明嘗試獲取鎖,退出代碼塊monitorexit表明釋放鎖。而在程序中,是沒法顯式釋放對同步監視器的鎖的,而會在以下4種狀況下釋放鎖。socket
一、當前線程的同步方法、代碼塊執行結束的時候釋放ide
二、當前線程在同步方法、同步代碼塊中遇到break 、 return 終於該代碼塊或者方法的時候釋放。spa
三、出現未處理的error或者exception致使異常結束的時候釋放.net
四、程序執行了 同步對象 wait 方法 ,當前線程暫停,釋放鎖
在如下兩種狀況不會釋放鎖。
一、代碼塊中使用了 Thread.sleep() Thread.yield() 這些方法暫停線程的執行,不會釋放。
二、線程執行同步代碼塊時,其餘線程調用 suspend 方法將該線程掛起,該線程不會釋放鎖 ,因此咱們應該避免使用 suspend 和 resume 來控制線程 。