同步块中List的数据重叠

时间:2012-08-29 15:30:08

标签: java synchronization thread-safety locking

我有一个方法:

private List<String> userCns = Collections.synchronizedList(new ArrayList<String>());
private List<String> recipients = Collections.synchronizedList(new ArrayList<String>());

public void sendEmailToLegalUsers() {
    try {
        synchronized (lock) {
            searchGroup();

            if(userCns.size() > 0) {
                for(String userCn : userCns) {
                    String mail = getUserMail(userCn);
                    if(mail != null) {
                        recipients.add(mail);
                    }
                }                   
            }

            String docName = m_binder.getLocal("docname");
            String docId = m_binder.getLocal("docid");
            String url = m_binder.getLocal("serverURL");

            if(recipients.size() > 0) {
                m_binder.addResultSet("LOI_EVIN_MAIL", getLoiEvinMailResultSet(docName, docId, url));

                for(String recipient : recipients) {
                    Log.info("Sending mail to: " + recipient);
                    InternetFunctions.sendMailToEx(recipient, "MH_LOI_EVIN_SEND_EMAIL", "Update Evin Law Compliance for the item: " + docName, m_service, true);
                }
            }
        }           
    } catch (Exception e) { 
        Log.info("Error occurred in LDAPSendMail: "+ e.getMessage());
    }   
}

现在可以从不同的线程调用此sendEmailToLegalUsers方法。我想知道它是否是锁定代码块的正确方法,以便列表中没有数据混淆的可能性?


编辑:全班:

package com.edifixio.ldapsendmail.handlers;

import intradoc.common.Log;
import intradoc.data.DataResultSet;
import intradoc.server.InternetFunctions;
import intradoc.server.ServiceHandler;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

public class LDAPSendMail extends ServiceHandler {

    private final Object lock = new Object();

    private String ldapURL;
    private String baseDN;
    private String groupDN;
    private String username;
    private String password;
    private DirContext context;

    private List<String> userCns = Collections.synchronizedList(new ArrayList<String>());
    private List<String> recipients = Collections.synchronizedList(new ArrayList<String>());

    public void sendEmailToLegalUsers() {
        try {
            synchronized (lock) {
                searchGroup();

                if(userCns.size() > 0) {
                    for(String userCn : userCns) {
                        String mail = getUserMail(userCn);
                        if(mail != null) {
                            recipients.add(mail);
                        }
                    }                   
                }

                String docName = m_binder.getLocal("docname");
                String docId = m_binder.getLocal("docid");
                String url = m_binder.getLocal("serverURL");

                if(recipients.size() > 0) {
                    m_binder.addResultSet("LOI_EVIN_MAIL", getLoiEvinMailResultSet(docName, docId, url));

                    for(String recipient : recipients) {
                        Log.info("Sending mail to: " + recipient);
                        InternetFunctions.sendMailToEx(recipient, "MH_LOI_EVIN_SEND_EMAIL", "Update Evin Law Compliance for the item: " + docName, m_service, true);
                    }
                }

                userCns.clear();
                recipients.clear();
            }           
        } catch (Exception e) { 
            Log.info("Error occurred in LDAPSendMail: "+ e.getMessage());
        }   
    }

    private String getUserMail(String userCn) throws NamingException {
        NamingEnumeration<SearchResult> searchResults = getLdapDirContext().search(userCn, "(objectclass=person)", getSearchControls());
        while (searchResults.hasMore()){
            SearchResult searchResult = searchResults.next();
            Attributes attributes = searchResult.getAttributes();
            Attribute mail = null;

            try {
                mail = attributes.get("mail");
            } catch (Exception e) {
                mail = null;
            }

            if(mail != null) {
                return (String)mail.get();
            }
        }
        return null;
    }

    private void searchGroup() throws NamingException {
        NamingEnumeration<SearchResult> searchResults = getLdapDirContext().search(groupDN, "(objectclass=groupOfUniqueNames)", getSearchControls());
        String searchGroupCn = getCNForBrand(m_binder.getLocal("brandId"), m_binder.getLocal("brandName"));

        while (searchResults.hasMore()) {
            SearchResult searchResult = searchResults.next();
            Attributes attributes = searchResult.getAttributes();
            Attribute groupCn = null;

            try {
                groupCn = attributes.get("cn");
            } catch (Exception e) {
                groupCn = null;
            }

            if(groupCn != null) {
                if(searchGroupCn.equals((String)groupCn.get())) {
                    Attribute uniqueMembers = attributes.get("uniqueMember");
                    for(int i = 0; i < uniqueMembers.size(); i++){
                        String uniqueMemberCN = (String) uniqueMembers.get(i);
                        userCns.add(uniqueMemberCN);
                    }
                    break;
                }
            }
        }
    }

    private DirContext getLdapDirContext() throws NamingException {
        if(context != null) {
            return context;
        }

        ldapURL = m_binder.getLocal("ldapUrl");
        baseDN = m_binder.getLocal("baseDN");
        groupDN = new StringBuilder().append("ou=").append(getAccountGroup(m_binder.getLocal("account"))).append(",").append("ou=groups,").append(baseDN).toString();
        username = m_binder.getLocal("username");
        password = m_binder.getLocal("password");

        Hashtable<String, String> environment = new Hashtable<String, String>();
        environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        environment.put(Context.PROVIDER_URL, ldapURL);
        environment.put(Context.SECURITY_PRINCIPAL, username);
        environment.put(Context.SECURITY_CREDENTIALS, password);

        context = new InitialDirContext(environment);

        return context;
    }

    private SearchControls getSearchControls() {
        SearchControls searchControls = new SearchControls();       
        searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
        return searchControls;
    }

    private String getCNForBrand(String brandId, String brandName) {        
        String[] brandIdSplittedArray = brandId.split("/");
        return new StringBuilder().append(brandIdSplittedArray[0]).append("-").append(brandIdSplittedArray[1]).append("-").
                append(brandIdSplittedArray[2]).append("-").append(brandName.replaceAll("\\s","")).append("-LU").toString();
    }

    private String getAccountGroup(String account) {        
        return account.split("/")[1];
    }

    private DataResultSet getLoiEvinMailResultSet(String docName, String docId, String url) {
        DataResultSet resultSet = new DataResultSet(new String[]{"DOCNAME", "DOCID", "URL"});
        Vector<String> vector = new Vector<String>();
        vector.add(docName);
        vector.add(docId);              
        vector.add(url);
        resultSet.addRow(vector);
        return resultSet;
    }
}

2 个答案:

答案 0 :(得分:1)

什么是lock?你在其他地方使用它吗?通常,您希望同步块非常小。如果你在任何地方都使用lock作为通用锁,那么你可能会阻止一个线程在完全不相关的区域(即没有共享资源争用的区域)中做一些有用的工作。

其次,recipients真的需要成为实例变量吗?您不断向recipients添加电子邮件,而不检查列表中是否已存在该电子邮件,这似乎很奇怪。我无法看到您正在清除recipients的任何代码。这是一个潜在的问题。如果您每次都要从头开始构建recipients,那么只需在方法中将其设置为局部变量即可。如果您确实需要访问该数据,则可以随时将其从userCns中删除。

recipients作为局部变量后,只需使用userCns作为锁定进行同步:

synchronized(userCns) {
   ...
}

编辑:您的代码显示您只使用recipients一次,而且它位于sendEmailToLegalUsers方法内。正如我所指出的,另一件事是你永远不会清除recipients所以这是你代码中的错误。由于您未在任何地方使用recipients,因此请将其设为sendEmailToLEgalUsers的局部变量。此外,只需在userCns上进行同步。您无需同步recipients;你可以在synchronized块中创建它。

答案 1 :(得分:1)

我愿意

private final List<String> userCns = new ArrayList<String>();
private final List<String> recipients = new ArrayList<String>();

synchronized(userCns) {
   // as Vivin suggests.
}

您不需要额外的锁定。