當你想在web應用中使用線程的時候-我們到底能走多遠系列(24)


我們到底能走多遠系列(24)

先不扯淡,先推薦:

  如果你熱愛英文技術原文的話,這個推薦的網站絕對讓你會想抱一抱他:http://www.salttiger.com/ (也許你早就知道啦) 再一次感謝那些樂於分享和貢獻的勇士們,雖然互聯網上我們互不相識,卻通過知識,我們建立了某種超越空間時光的特殊關系,想想,這真的很有趣。

扯淡:

  最近朋友在老家工作量一年,又跑來城市奮斗。可是納悶的是我數來數去,當時留在城市的人數正在逐年的下降,可這貨怎么還會來呢?

最近,想比較深入的學習事務,可是看了好多文章,卻越看越糊塗,有想起去看別的東西,有點三心兩意的感覺了。真心希望有人帶一下,輕松一點,唉。神,賜我一個大牛吧!

現在的公司,雖然是國內的,工作管理上較為開放,很多事可以自己決定,有時候自己會准備好幾個方案,和同事討論一下,選個比較優的,再去寫代碼,到也不錯。

 

主題:

  初入web的時候,我們總是會被教育,web應用無需關心線程的問題,學好基本的框架,就可以上手啦。

  其實實際項目中使用多線程的情況是很正常的,在業務復雜的應用程序中,比如如果一個業務非常耗時,我們只好采用異步的方式,避免影響web端的展示,再有定時監控數據庫字段的變化的業務,或者是batch處理(半夜處理數據庫.....)也就是定時任務啦等等。

我就把最近遇到的問題展現給各位,希望大家能給點好的意見,我都會嘗試使用,並應用到項目中去。

1,發郵件問題

  項目中,注冊完畢后,需要向用戶的郵箱發送郵件,開始的代碼就是把發郵件的邏輯封裝在service層,然后action層調用完畢后,返回頁面,展現頁面。實際測試還沒有發現頁面跳轉太慢的情況,但是為了安全起見,還有一個原則就是我們不要把一些會拋諸runtimeException的邏輯放在和展現頁面的邏輯一起,異步是唯一的選擇。

多線程的實現是利用spring的框架:

線程池bean配置:

      <bean id="mailTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <property name="corePoolSize" value="3"/>
        <property name="maxPoolSize" value="10"/>
    </bean>

直接把mailTaskExecutor注入進service層,就可以使用啦!

一下是發送郵件的簡單代碼:

    public boolean sendMail4D(String email,String basePath, String cstmName,String userName,String passWord) {
        final MailSenderInfo mailInfo = getMailInfo(email);
        final Map model = new HashMap();
        model.put("basePath", basePath);
        model.put("cstmName", cstmName);
        model.put("username", userName);
        model.put("password", passWord);
        model.put("dealerLogin", dealerLogin);
        model.put("customerTel", customerTel);
        model.put("cusEmailAddress", cusEmailAddress);
        mailTaskExecutor.execute(new Runnable(){
               public void run(){
                   String result;
                try {
                    result = VelocityEngineUtils.mergeTemplateIntoString(velocityEngine,
                                                                "dealer-reg-send-mail.vm", model);
                    mailInfo.setContent(result);
                    SimpleMailSender sms = new SimpleMailSender();
                    sms.sendHtmlMail(mailInfo);// 發送文體格式
                } catch (Exception e) {
                    log.error("send mail failed,there has a Exception");
                    log.error(e.toString());
                    e.printStackTrace();
                }

               }
        });
        return true;
    }

  這樣以來,在action層調用這個方法后就不用等待,郵件發送完畢后的返回了,唯一的壞處是,我們不知道郵件是否發送成功,這可能需要更多其他的代碼來彌補了。

 

2,數據庫字段監控問題

問題描述:

比如我們進行一個發布評論的操作,發布這個過程是通過webservice來調用另一個系統來實現的,在調用前我們把這個數據的字段置為發布中,對方回調成功后就置為發布成功,失敗就是發布失敗,但是有一種情況那就是對方出現異常,回調沒來調,這樣會導致這條數據一直為發布中... 這樣的數據就需要我這邊來判斷,然后把它置為發布失敗!

開始的想法:

每次調用對方的發布接口時,都啟用一個線程,這個線程每隔一分鍾檢查數據庫中這條數據的標志位,檢查三次,如果始終是發布中,就把它置為發布失敗

ok,開始去實現了,想到前面提到的spring框架提供的線程池,也不是很難了吧。

一下是線程的代碼示例:(spring的線程配置與前面差不多)

import org.apache.log4j.Logger;

import com.syezon.webapp.constant.BusinessConstants;
import com.syezon.webapp.dao.ReleaseDao;
import com.syezon.webapp.model.Release;

public class CheckAdvStatusThread extends Thread{
    
    public static Logger log = Logger.getLogger(CheckAdvStatusThread.class);
    private ReleaseDao releaseDao;
    private Long releaseId;
    
    public CheckAdvStatusThread(Long releaseId, ReleaseDao releaseDao){
        this.releaseId = releaseId;
        this.releaseDao = releaseDao;
    }

    public void setReleaseDao(ReleaseDao releaseDao) {
        this.releaseDao = releaseDao;
    }

    public void run() {
        
        int i = 0;
        for ( ; i < 3; i++) {
            try {
                // 半分鍾
                Thread.sleep(30000);
                
                Release release = releaseDao.getById(releaseId);
                if(release.getStatus() == BusinessConstants.RELEASE_STATUS_PUBING){
                    continue;
                }else{
                    break;
                }
            } catch (InterruptedException e) {
                log.error("there has a InterruptedException");
                e.printStackTrace();
            }
        }
        // 一分半鍾
        if(i == 3){
            releaseDao.setStatus(releaseId, BusinessConstants.RELEASE_STATUS_PUB_FAIL);
        }
    }

}

代碼也沒什么好解釋了,特別要注意的是:構造方法,dao層的bean是通過構造時進來的,為什么不利用spring注入呢?事實上,試過的朋友應該多知道,在線程類中是無法注入的,可能線程啟動的方式繞過了一個正常實例產生時需要的流程吧,解決方法有:

在用多線程的時候,里面要用到Spring注入服務層,或者是邏輯層的時候,一般是注入不進去的。具體原因應該是線程啟動時沒有用到Spring實例不池。所以注入的變量值都為null。

如果在run方法里面加載application.xml,來取得bean時,由於重復加載,一般情況下會耗盡內存,拋出內存溢出錯誤。所以這的確是一個很頭痛的問題。

有一個方法可以解決注不進去的問題。就是在聲明變量的時候,前面加static。這樣在初始化的時候它就會加載application.xml,得到bean。

關於這個問題的根本機制沒有作深入的研究,好在問題解決了。

從這個例子體會到林信良說過的,沒有一個技術是完美的,不要為了Spring而Spring。不要為了注入而注入。

我沒有使用以上方式是因為,我嘗試了一下,不可行啊,但是我網上尋找的答案太過一直,所以我認為我是個特例,如果你也遇到類似的問題,大可以先嘗試一下上面的方法。

 

以上方法的問題:並發量大的時候會導致大量線程的啟用和銷毀,在3分鍾的時間里,真的難以想象不斷創建線程會發生什么,想想也有點怕怕啊!

親,如果是你,你怕嗎?

 

下班前的指導:

上級給出的意見是這樣,采用一個隊列(說白了,不就是LinkList嘛),然后啟動一個線程,這個線程對着個隊列進行檢測。每次發布的時候向這個隊列里塞信息,線程根據隊列中的信息,判斷發布中的狀態是否維持到了超時的范圍,就把它置為發布失敗,隊列刪除這信息

上面的想法,其實很不錯,如此解決了第一種方式帶來的大部分問題。

回去想了好久,我想問一下你們,你們有類似的經驗嗎,給點提示,例子什么的啊~~

 

經過和同事的溝通,對方建議采用定時器更加靠譜!

目前,使用的是定時器方式解決的:

spring也支持定時器嘛,配置如下:

      <!-- 需要執行的任務 -->
    <bean id="checkAdvStatusJob" class="org.springframework.scheduling.quartz.JobDetailBean">
        <property name="jobClass" value="com.syezon.webapp.util.CheckAdvStatusMonitor"/>
        <property name="jobDataAsMap">   
        <map>
            <entry key="releaseDao">
                <ref bean="releaseDao"/>
            </entry>
        </map>  
    </property>
    </bean>
    <!-- 對任務的參數的設置 -->
    <bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
        <property name="jobDetail" ref="checkAdvStatusJob" />
        <property name="startDelay" value="180000" /><!--啟動3分鍾后再開始-->
        <property name="repeatInterval" value="180000" /><!--每3分鍾跑一次-->
    </bean>
    <!-- 啟動任務 -->
    <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <!--<property name="triggers" ref="cronTrigger" />-->
        <property name="triggers" ref="simpleTrigger" />
    </bean>
    

  注意checkAdvStatusJob的配置,正真的邏輯我們是寫在jobClass里的。注入到jobClass的dao層bean只能通過上面的方式實現,不能用普通spring的property 去實現哦!releaseDao是注入到jobClass里的!下面的配置就不解釋啦。

 

CheckAdvStatusMonitor類,簡單的示例:
import java.util.Date;
import java.util.List;

import org.apache.log4j.Logger;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

import com.syezon.webapp.constant.BusinessConstants;
import com.syezon.webapp.dao.ReleaseDao;
import com.syezon.webapp.model.Release;

public class CheckAdvStatusMonitor extends QuartzJobBean{
    public static Logger log = Logger.getLogger(CheckAdvStatusMonitor.class);
    private ReleaseDao releaseDao;
    
    @Override
    protected void executeInternal(JobExecutionContext arg0)
            throws JobExecutionException {
        
        log.info("CheckAdvStatusMonitor begin");
        
        // 查詢全部發布中狀態的發布信息
        List<Release> releases = releaseDao.getByStatus(BusinessConstants.RELEASE_STATUS_PUBING);
        if(releases == null || releases.size() <= 0){
            log.info("CheckAdvStatusMonitor end - there has no publishing release");
            return;
        }
        for (Release release : releases) {
            Date createDate = release.getReleaseTime();
            Date currentDate = new Date();
            long l = currentDate.getTime() - createDate.getTime();
            // 超過三分鍾
            if(l > 180000){
                log.info("CheckAdvStatusMonitor change a status");
                releaseDao.setStatus(release.getId(), BusinessConstants.RELEASE_STATUS_PUB_FAIL);
                log.info("CheckAdvStatusMonitor change a status, releaseId = release.getId()");
            }
        }
        log.info("CheckAdvStatusMonitor end");
    }

    public void setReleaseDao(ReleaseDao releaseDao) {
        this.releaseDao = releaseDao;
    }

}

 

好了,事情描述完啦,親,你有沒有好的建議? 

 

 

 

讓我們繼續前行

----------------------------------------------------------------------

努力不一定成功,但不努力肯定不會成功。
共勉。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM