我在实际项目中写过的多线程
多线程中有分主线程和子线程,有用到对象队列ObjectQueue,首先看一下ObjectQueue这个类:
import java.util.ArrayList; import java.util.NoSuchElementException; /** * Object Queue which support multi-thread consumer/supplier work */ public class ObjectQueue implements java.io.Serializable{ //private Logger logger; private int maxLength; private ArrayList queue; private long waitTime; private boolean inPreparing; private boolean dying;// when queue is requested to die, this will set to true; private String name; /** * @param maxLength Maximum length of the queue, if element size is equal to this one, we * say queue is full. If no positive integer specified, will take as unlimited. */ public ObjectQueue(int maxLength){ this(maxLength, 0, "queue"); } /** * Wait time out in miliseconds * @param maxLength * @param wait time out in miliseconds, if 0, will wait until programically interrupted * @param queueName name of the queue */ public ObjectQueue(int maxLength, long wait, String queueName){ this.maxLength= maxLength; queue=new ArrayList(); inPreparing=false; dying =false; this.waitTime=wait; name=queueName; //logger= LoggerFactory.getLogger(this.getClass().getName()+"_"+queueName); } public String getName(){ return name; } public int getMaxLength(){ return maxLength; } /** * Change max length of the queue. * @param maxLength */ public void setMaxLength(int maxLength){ this.maxLength = maxLength; } /** * How long will the supplier wait until there are space to store the object * and the customer wait until there are objects to obtain * @param time */ public void setWaitTime(long time){ this.waitTime=time; } /** * @return size of current queue. */ public int size(){ return queue.size(); } /** * add a new object into queue, if queue is full, the operation will be * locked until element been retrieved out */ public synchronized void addElement(Object obj){ while( !dying && isFull()){ try { wait(waitTime); } catch (InterruptedException e) { } } queue.add(obj); notifyAll(); } public synchronized boolean hasMoreElements(){ if( !dying && queue.size() > 0 ) return true; while ( !dying && queue.size() == 0 && inPreparing ){ try { log("wait to check hasMoreElements"); wait(waitTime); log("check hasMoreElements: quesize="+queue.size()+", inpreparing="+inPreparing); } catch (InterruptedException e) { } } notifyAll(); return ( queue.size()>0); } /** * Tell that data is just preparing, yet not OK * This is specillay important if you want consumers wait even when * suppliers has nothing to supply. */ public void setInDataPreparing(boolean b){ inPreparing =b; } /** * after next element being retrieved, the object will be remove from queue * @throws NoSuchElementException if could not get next element either because * queue is empty or wait timeout */ public synchronized Object nextElement() throws NoSuchElementException { if ( !dying && queue.size() == 0 && inPreparing ){ try { log("wait to getNextele"); long b= System.currentTimeMillis(); wait(waitTime); log("out wait getNextele:"+queue.size() +", inprepare: "+inPreparing+" wait "+ (System.currentTimeMillis()- b)/1000+" sec, limit time="+(waitTime/1000)); } catch (InterruptedException e) { } } if( queue.size() > 0){ try{ Object obj= queue.remove( queue.size() -1); notifyAll(); return obj; }catch(Exception e){ throw new NoSuchElementException("Error:"+e); } } throw new NoSuchElementException("No element found or time out"); } /** * If max length specified by Constructor is -1 or 0, always return false */ public boolean isFull(){ if (maxLength <1 ) return false; return (queue.size() >= maxLength); } public boolean isEmpty(){ return (queue.size() == 0); } /** * wait current thread until queue size is bigger(not equal) than specified value, * this method is helpful when supplier finished and want to sweep trash now */ public synchronized void waitOnSizeBigger(int size){ if(size <0) return; while( queue.size() > size){ try { wait(waitTime); } catch (InterruptedException e) { } } notifyAll(); } /** * this queue is no longer used */ public synchronized void destroy(){ dying=true; // queue.clear(); can not clear queue because this would probably called in // normal situation when supplier finish while consumer is // still extracting objects notifyAll(); log("ObjectQueue destroied"); } private void log(String s){ //logger.debug(s); } public String toString(){ return getName()+", maxlength="+ getMaxLength()+",current="+ size(); } }
然后看一下我的多线程:
import java.sql.Connection; import java.util.ArrayList; import java.util.NoSuchElementException; import org.json.JSONArray; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.agilecontrol.nea.core.query.QueryEngine; import com.agilecontrol.nea.util.NDSException; import com.agilecontrol.nea.util.ObjectQueue; /** * 多线程处理每个商品的铺量 * 铺量规则:不同评分 商品的订量不同,不评分或0分时商品不铺量,2分或3分时 对应评分组里的店铺订单会对这个商品下量,4分5分时 刷单买手的所有下级买手订单都会对这个商品下量, * 尺码比例先以B_SIZE_UPDATE表为准,找不到再以B_SIZE_ALLOC表为准,再找不到则在第一个尺码下量 * 实现逻辑:构造一个队列对象,队列中有多个商品对象,多个线程并发对队列中的商品铺量,每个线程有自己的connection,当有一个线程有异常时,所有线程包括主线程全部回滚 * @author ye.huimin */ @SuppressWarnings({ "rawtypes", "unchecked", "static-access" }) public class SaveGroupQtyByFav { private static final Logger logger = LoggerFactory.getLogger(SaveGroupQtyByFav.class); /** * 订货会id */ private int fairId; /** * 当前主买手id,他有多个下级买手 */ private int funitId; /** * 当前主卖手的user id */ private int userId; /** * 当前商品id、对应评分star和所在组名groupnm,type标明是iPad端还是portal后台的操作:[{pdtid: 1, star: 5, groupnm: 'G1', type: 'ipad'}, {pdtid: 2, star: 3, groupnm: 'G2', type: 'ipad'}, ...] * 或者只是商品id和type:[{pdtid: 1, type: 'portal'},{pdtid: 2, type: 'portal'},...] */ private JSONArray pdtStar; /** * 线程数,是参数 可控 */ private int syncThreadCount; /** * 需要以组为单位,按评分铺量的商品数 */ private int syncPdtCount; /** * 铺量失败的flag */ private boolean isFailed = false; /** * 对象队列 */ private ObjectQueue queueForSync; /** * 线程的Connection集合 */ private ArrayList<Connection> connList; private QueryEngine engine; /** * @param userId 用户ID * @param funitId 买手ID * @param fairId 订货会ID * @param pdtStar 当前商品id、对应评分star和所在组名groupnm,type标明是iPad端还是portal后台的操作:[{pdtid: 1, star: 5, groupnm: 'G1', type: 'ipad'}, {pdtid: 2, star: 3, groupnm: 'G2', type: 'ipad'}, ...] * 或者只是商品id和type:[{pdtid: 1, type: 'portal'},{pdtid: 2, type: 'portal'},...] * @throws Exception */ public SaveGroupQtyByFav(int userId, int funitId, int fairId, JSONArray pdtStar) throws Exception { this.fairId = fairId; this.funitId = funitId; this.userId = userId; this.pdtStar = pdtStar; this.engine = QueryEngine.getInstance(); this.syncThreadCount = FairConfig.THREADS_SAVE_GROUP_QTY; this.connList = new ArrayList<Connection>(); } /** * 创建队列,多线程执行队列中每个对象的铺量动作 * @return * @throws Exception */ public String multiThreadSaveQty() throws Exception { long startTime = System.currentTimeMillis(); String err = null; try { // 线程数默认为4 if (syncThreadCount <= 0) syncThreadCount = 4; // 创建队列 queueForSync = new ObjectQueue(-1, 500L, "savegroupqtyqueue"); // no limit for input queueForSync.setInDataPreparing(true); // 队列中添加对象 for (int i = 0; i < pdtStar.length(); i++) { queueForSync.addElement(pdtStar.getJSONObject(i)); } syncPdtCount = queueForSync.size(); // 创建子线程 ThreadGroup syncg = new ThreadGroup("SavaGroupQty"); for (int i = 0; i < syncThreadCount; i++) { Connection conn = engine.getConnection(); connList.add(conn); SyncOnePdt syncOne = new SyncOnePdt("groupQty" + i, this, conn); Thread thread = new Thread(syncg, syncOne); thread.start(); } // 主线程等待 while (queueForSync.hasMoreElements() && !isFailed) { try { Thread.currentThread().sleep(500); } catch (Throwable tx) { logger.debug("fail to sleep when queue for zip:" + tx.getMessage()); } } // 销毁队列 queueForSync.destroy(); // 如果存在失败的对象,抛出异常 if (isFailed) { long duration = (long) ((System.currentTimeMillis() - startTime) / 1000.0); err = "刷单用户id = " + funitId + "的商品铺量失败,详见log日志。耗时:" + duration + "秒。"; throw new NDSException(err); // Fail to save quantity of products. } } finally { for (int i = 0; i < connList.size(); i++) { if(connList.get(i)!=null) try{ connList.get(i).close(); } catch(Throwable tx) {} } if (null != err) { return err; } } long duration = (long) ((System.currentTimeMillis() - startTime) / 1000.0); String msg = "耗时" + duration + "秒," + (this.isFailed ? "刷单用户id = " + funitId + "的商品铺量失败,详见任务日志" : "完成商品铺量"); return msg; } /** * 下一个商品对象 * @return */ JSONObject nextQty() { return (JSONObject) queueForSync.nextElement(); } /** * 铺量成功的下一步 * @param pdt 当前商品id、对应评分star和所在组名groupnm,type标明是iPad端还是portal后台的操作:{pdtid: 1, star: 5, groupnm: 'G1', type: 'ipad'},或者只是商品id和type:{pdtid: 1, type: 'portal'} */ synchronized void onQtyComplete(JSONObject pdt) { syncPdtCount--; if (syncPdtCount == 0) this.queueForSync.setInDataPreparing(false); } /** * 铺量失败的下一步 * @param pdt 当前商品id、对应评分star和所在组名groupnm,type标明是iPad端还是portal后台的操作:{pdtid: 1, star: 5, groupnm: 'G1', type: 'ipad'},或者只是商品id和type:{pdtid: 1, type: 'portal'} * @param tx */ synchronized void onQtyFailed(JSONObject pdt, Throwable tx) { isFailed = true; logger.debug("pdtId = " + pdt.optString("pdtid") + " failed to save qty. " + tx.getMessage()); syncPdtCount--; if (syncPdtCount == 0) this.queueForSync.setInDataPreparing(false); } /** * 单个save pdt 线程 * 如果有某个商品保存失败,线程退出循环;如果没有,线程执行到队列中没有对象后退出循环 * 2017年8月28日 下午15:33:50 * @author ye.huimin */ class SyncOnePdt implements Runnable { private final Logger logger = LoggerFactory.getLogger(SyncOnePdt.class); private Connection conn; private SaveGroupQtyByFav groupQty; private String id; private int fairId; private int funitId; private int userId; /** * @param id 线程名 * @param groupQty * @throws Exception */ public SyncOnePdt(String id, SaveGroupQtyByFav groupQty, Connection conn) throws Exception { this.id = id; this.groupQty = groupQty; this.userId = groupQty.userId; this.funitId = groupQty.funitId; this.fairId = groupQty.fairId; this.conn = conn; } @Override public void run() { logger.debug("Sync " + id + " started"); long beginTime = System.currentTimeMillis(); JSONObject pdt; while (!groupQty.isFailed) { try { pdt = groupQty.nextQty(); if (pdt == null) break; } catch (NoSuchElementException ex) { logger.debug("No elements found for " + id); break; } try { executeOne(pdt); groupQty.onQtyComplete(pdt); } catch (Throwable tx) { logger.error("Fail to save pdt, id = " + pdt.optInt("pdtid"), tx); groupQty.onQtyFailed(pdt, tx); break; } } double duration = (System.currentTimeMillis() - beginTime) / 1000.0; logger.debug("Sync " + id + " ends, druation: " + duration + " seconds"); } /** * 每一个商品的保存订量动作 * @param pdt 当前商品id、对应评分star和所在组名groupnm,type标明是iPad端还是portal后台的操作:{pdtid: 1, star: 5, groupnm: 'G1', type: 'ipad'},或者只是商品id和type:{pdtid: 1, type: 'portal'} * 117410JZ802405,117400B1707411,117410AZ404203,117410F1114510,117400C3515201,117410F0103515 * @throws Exception */ private void executeOne(JSONObject pdt) throws Exception { String type = pdt.optString("type", "portal"); // 默认是后端的,则商品会以b_fair_fav表里的评分铺量到默认组里的所有下级买手订单上 int pdtId = pdt.optInt("pdtid", 0); ArrayList param = new ArrayList(); param.add(funitId); // 买手id param.add(pdtId); // 商品id if (type.equals("portal")) { engine.executeStoredProcedure("b_fo_paving", param, conn); } if (type.equals("ipad")) { int star = pdt.optInt("star", 0); // 评分 String groupnm = pdt.optString("groupnm", ""); // 组名 param.add(star); param.add(groupnm); engine.executeStoredProcedure("b_fo_cllect", param, conn); } } } }
最后调用多线程:
protected void executeUpdateQty(ArrayList<Integer> pdtIds, JSONObject qtyObject, JSONArray pdtStar, String idArr) throws Exception { int fairId = context.getFairId(); int funitId = context.getFUnitId(); int userId = context.getUserId(); // 使用多线程铺量 SaveGroupQtyByFav saveQty = new SaveGroupQtyByFav(userId, funitId, fairId, pdtStar); saveQty.multiThreadSaveQty(); ... }