线程启动导致主线程卡死,tomcat无法启动

曾杰 发布于 2012/02/24 10:22
阅读 3K+
收藏 0
 

package com.***.eeducation.portal.resource.utils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.DisposableBean;

import com.***.eeducation.portal.resource.common.ISystemConstants;
import com.***.eeducation.portal.resource.domain.constant.IResultCode;
import com.***.eeducation.portal.resource.log.ServerLogger;
import com.***.eeducation.portal.resource.service.ServiceUtils;
import com.***.eeducation.portal.resource.service.ServiceUtils.ServerType;
import com.***.eeducation.server.common.response.Response;
import com.***.eeducation.server.manager.domain.entity.OperationLog;
import com.***.eeducation.server.manager.domain.request.system.AddOperationLogReq;

/**
 * 
 * <li><b>介绍:日志队列记录工具</b> <li>入口:startRecord() <li><b>说明:</b> 此工具以异步线程的方式将日志队列刷新至服务器
 * 
 * @author zengj
 * @version [版本号, 2012-2-15]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
public class OperateLogRecorder implements DisposableBean
{
    private static final ServerLogger logger = new ServerLogger(OperateLogRecorder.class);
    
    private static final OperateLogRecorder INSTANCE = new OperateLogRecorder();
    
    /** 守护线程,用来请求Server保存日志 */
    private Thread deamonThread;
    
    /** 是否是立即刷新 */
    private boolean flushNow;
    
    private boolean available;
    
    private Object[] threadLock = new Object[0];
    
    /** 日志缓存队列 */
    private Queue<OperationLog> cacheQueue;
    
    /** 日志最大缓存数 */
    private int maxCacheSize;
    
    /** 日志最小缓存数(如果不是超过最小缓存数则不进行刷新到服务器的操作,强制刷新除外) */
    private int minCacheSize;
    
    /** 刷新日志队列间隔 */
    private long flushInterval;
    
    private OperateLogRecorder()
    {
        synchronized (cacheQueue)
        {
            cacheQueue = new LinkedList<OperationLog>();
        }
        initThread();
    }
    
    /**
     * 确定当前是否是有效的状态,可以进行日志的排队
     * 
     * @see [类、类#方法、类#成员]
     */
    private void ensureAvailable()
    {
        if (!available)
        {
            throw new RuntimeException("OperateLog Recorder now is invaild");
        }
    }
    
    /**
     * 初始化守护线程
     * 
     * @see [类、类#方法、类#成员]
     */
    private final void initThread()
    {
        synchronized (threadLock)
        {
            available = false;
            deamonThread = new Thread(new RecordRunner());
            deamonThread.setName("Record-Deamon-Thread");
            // 设置最小优先级
            deamonThread.setPriority(Thread.MIN_PRIORITY);
            deamonThread.setDaemon(true);
            deamonThread.setUncaughtExceptionHandler(new UncaughtExceptionHandler()
            {
                public void uncaughtException(Thread t, Throwable e)
                {
                    logger.error("Error Occur At" + t.getName(), e);
                    initThread();
                    startRecord();
                }
            });
        }
    }
    
    /**
     * 启动线程开始记录
     * 
     * @see [类、类#方法、类#成员]
     */
    public void startRecord()
    {
        this.flushInterval = ServerConfig.getInt("flushInterval", 10000);
        this.maxCacheSize = ServerConfig.getInt("maxCacheSize", 100);
        this.minCacheSize = ServerConfig.getInt("minCacheSize", 50);
        logger.debug("LogRecorder Init Start...[flushInterval:" + flushInterval + ",maxCacheSize:" + maxCacheSize
            + ",minCacheSize:" + minCacheSize + "]");
        synchronized (threadLock)
        {
            deamonThread.start();
            while (!available)
            {// 直到线程运行后返回
            }
            logger.debug("Recorder Startup Ok..........");
        }
    }
    
    /**
     * 将操作日志添加到保存队列
     * 
     * @param log
     * @see [类、类#方法、类#成员]
     */
    public void addOperationLog(OperationLog log)
    {
        ensureAvailable();
        synchronized (cacheQueue)
        {
            cacheQueue.add(log);
            // 如果查过最大的保存数,唤醒线程进行保存
            if (cacheQueue.size() > maxCacheSize)
            {
                flushNow = true;
                cacheQueue.notify();
            }
        }
    }
    
    /**
     * 强制刷新当前日志队列
     * 
     * @see [类、类#方法、类#成员]
     */
    public void forceFlush()
    {
        synchronized (cacheQueue)
        {
            flushNow = true;
            if (!cacheQueue.isEmpty())
            {
                cacheQueue.notify();
            }
        }
    }
    
    /**
     * 获取唯一实例
     * 
     * @return
     * @see [类、类#方法、类#成员]
     */
    public static OperateLogRecorder getInstance()
    {
        return INSTANCE;
    }
    
    /**
     * 日志保存Runner
     * 
     * @author zengj
     * @version [版本号, 2012-2-14]
     * @see [相关类/方法]
     * @since [产品/模块版本]
     */
    private class RecordRunner implements Runnable
    {
        
        public void run()
        {
            available = true;
            while (available)
            {
                List<OperationLog> list = null;
                synchronized (cacheQueue)
                {
                    try
                    {
                        // 如果不是立即刷新阻塞当前线程等待指定的时候后进行刷新
                        if (!flushNow)
                        {
                            cacheQueue.wait(flushInterval);
                        }
                    }
                    catch (InterruptedException e)
                    {
                        logger.error("Thread Wait Error", e);
                    }
                    logger.debug("Recorder Start To Check Queue.....[Size:" + cacheQueue.size() + "]");
                    if (!cacheQueue.isEmpty() && (flushNow || cacheQueue.size() > minCacheSize))
                    {
                        list = new ArrayList<OperationLog>();
                        OperationLog log = null;
                        while ((log = cacheQueue.poll()) != null)
                        {
                            list.add(log);
                        }
                    }
                    flushNow = false;// 复位
                }
                if (CollectionUtils.isEmpty(list))
                {
                    continue;
                }
                saveLogs(list);
            }
        }
        
        /**
         * 保存日志
         * 
         * @see [类、类#方法、类#成员]
         */
        private void saveLogs(List<OperationLog> list)
        {
            logger.debug("Start to save operationLogs...");
            AddOperationLogReq req = new AddOperationLogReq();
            req.setOperationLogList(list);
            try
            {
                // 发送请求记录操作日志
                Response rsp = (Response)ServiceUtils.sendForGetResponse(ServerType.MANAGER,
                    "addOperationLog",
                    req,
                    Response.class);
                if (rsp.getServerResult().getResultCode() != IResultCode.ISystemResultCode.SUCCESS)
                {
                    logger.error("Record OperateLog Fail:[Code:" + rsp.getServerResult().getResultCode() + ",Message:"
                        + rsp.getServerResult().getResultMessage() + "]");
                    backUpOperateLog(list);
                }
                else
                {
                    logger.debug("OperationLogs saved success...[TotalCount:" + list.size() + "]");
                }
            }
            catch (Exception e)
            {
                logger.error("Record OperateLog Fail", e);
                backUpOperateLog(list);
            }
            
        }
        
        /**
         * 当保存操作日志失败的时候将操作日志备份到本地等待以后处理
         * 
         * @param log
         * @see [类、类#方法、类#成员]
         */
        private void backUpOperateLog(List<OperationLog> logs)
        {
            logger.debug("Record Log Fail Then Backup Logs At Local......");
            // TODO 待补充
            File backUpDir = new File(System.getProperty(ISystemConstants.IREAD_WEBAPP_HOME) + "logsBackup");
            boolean flag = true;
            if (backUpDir.exists())
            {
                if (backUpDir.isFile())
                {
                    flag = backUpDir.delete();
                }
            }
            else
            {
                flag = backUpDir.mkdir();
            }
            if (!flag)
            {
                logger.debug("Backup Log Fail With File......");
                return;
            }
            long current = System.currentTimeMillis();
            // 拼接备份的文件名
            StringBuilder backFilePath = new StringBuilder(backUpDir.getAbsolutePath());
            backFilePath.append(File.separator);
            backFilePath.append("logs").append(current);
            backFilePath.append(".bak");
            File backupFile = new File(backFilePath.toString());
            ObjectOutputStream out = null;
            try
            {
                // 将日志暂时序列化到本地
                backupFile.createNewFile();
                out = new ObjectOutputStream(new FileOutputStream(backupFile));
                out.writeObject(logs.toArray());
                out.flush();
                logger.debug("Logs Backup Success File:" + backupFile.getAbsolutePath());
            }
            catch (IOException e)
            {
                logger.error("Backup Log Fail:" + e.getMessage(), e);
            }
            finally
            {
                if (out != null)
                {
                    try
                    {
                        out.close();
                    }
                    catch (IOException e)
                    {
                        logger.error("Close File Fail:" + e.getMessage(), e);
                    }
                }
            }
        }
        
    }
    
    /**
     * 销毁线程,停止记录,将日志强制刷新到Server
     * 
     * @see [类、类#方法、类#成员]
     */
    public void destroy()
    {
        if (!available)
        {
            return;
        }
        logger.debug("Recorder Destory And Force Flush.....");
        this.available = false;
        this.flushNow = true;// 刷新
        synchronized (cacheQueue)
        {// 如果线程等待,提醒
            cacheQueue.notify();
        }
        try
        {
            // 等待线程结束后退出
            deamonThread.join();
        }
        catch (InterruptedException e)
        {
            logger.error("Thread Join Error", e);
        }
    }
}

个是公司要求做的一个后台日志记录类,调用的startRecord是在Servlet启动中调用的,但是有时候会出现将主线程锁死的问题,导致整个工程无法启动,而且这种问题是偶现的,发上来求高手指点下。

初步判断是那个while(!available)的原因,但是又不明白到底怎么回事

加载中
0
张海雷
出现这种情况的时候 用jstack查看线程 以及锁竞争情况
0
曾杰
曾杰
没有人来看啊。。。。我自己顶顶
0
曾杰
曾杰
再补充下。。。。在linux上偶现,开发windows下没有出现过这种情况
0
曾杰
曾杰
再来顶。。。。顶顶更健康
0
JavaGG
JavaGG

不要用LinkedList用ConcurrentLinkedQueue

说实话,代码太多,没怎看明,不过你试试用ConcurrentLinkedQueue这个吧

0
曾杰
曾杰
恩,第一次出现的时候用jstack看了堆栈信息,发现就是阻塞在startRecord方法,其他线程全部是挂起的,问题是这个方法不知道除了什么问题
返回顶部
顶部