如何在多线程环境中识别非线程安全的代码?

时间:2016-01-08 16:51:14

标签: java multithreading design-patterns

我使用纯JSPJavaBeans设计并实现了一个基于传统MVC Model 1架构的简单Webstore(是的,我仍然在我的宠物项目中使用该传统技术;))。

我使用DAO设计模式为webstore实现我的持久层。但我不确定我是否在DAO层中正确实现了类。我特别关注QueryExecutor.javaDataPopulator.java类(如下所述)。这两个类中的所有方法都定义为static,这使我想到这是多线程环境中的正确方法。因此,我对静态方法有以下问题。

  1. 当多个用户尝试使用不同的产品结帐时是否会出现同步问题?如果回答上述问题是肯定的,那么我该如何实际重现此同步问题?
  2. 是否有任何测试/跟踪工具可以实际显示特定的代码片段/可能会在多线程环境中产生同步问题?我是否可以看到User1尝试访问Product-101但由于非线程安全代码而显示Product-202
  3. 假设存在同步问题;是否应将这些方法设置为非静态且类是可立即的,以便我们可以使用new运算符创建实例或者是否应在非线程安全代码周围放置synchronized块?
  4. 请指导。

    MasterDao.java

    public interface MasterDao {
        Product getProduct(int productId) throws SQLException;
    }
    

    BaseDao.java

    public abstract class BaseDao {
        protected DataSource dataSource;
        public BaseDao(DataSource dataSource) {
            this.dataSource = dataSource;
        }
    }
    

    MasterDaoImpl.java

    public class MasterDaoImpl extends BaseDao implements MasterDao {
    
        private static final Logger LOG = Logger.getLogger(MasterDaoImpl.class);
    
        public MasterDaoImpl(DataSource dataSource) {
            super(dataSource);
        }
    
        @Override
        public Product getProduct(int productId) throws SQLException {
            Product product = null;
            String sql = "select * from products where product_id= " + productId;
            //STATIC METHOD CALL HERE, COULD THIS POSE A SYNCHRONIZATION ISSUE ??????
            List<Product> products = QueryExecutor.executeProductsQuery(dataSource.getConnection(), sql);
            if (!GenericUtils.isListEmpty(products)) {
                product = products.get(0);
            }
            return product;
        }
    
    }
    

    QueryExecutor.java

    public final class QueryExecutor {
    
        private static final Logger LOG = Logger.getLogger(QueryExecutor.class);
    
        //SO CANNOT NEW AN INSTANCE
        private QueryExecutor() {
        }
    
        static List<Product> executeProductsQuery(Connection cn, String sql) {
            Statement stmt = null;
            ResultSet rs = null;
            List<Product> al = new ArrayList<>();
    
            LOG.debug(sql);
    
            try {
                stmt = cn.createStatement();
                rs = stmt.executeQuery(sql);
                while (rs != null && rs.next()) {
                    //STATIC METHOD CALL HERE, COULD THIS POSE A SYNCHRONIZATION ISSUE ???????
                    Product p = DataPopulator.populateProduct(rs); 
                    al.add(p);
                }
                LOG.debug("al.size() = " + al.size());
                return al;
            } catch (Exception ex) {
                LOG.error("Exception while executing products query....", ex);
                return null;
            } finally {
                try {
                    if (rs != null) {
                        rs.close();
                    }
                    if (stmt != null) {
                        stmt.close();
                    }
                    if (cn != null) {
                        cn.close();
                    }
                } catch (Exception ex) {
                    LOG.error("Exception while closing DB resources rs, stmt or cn.......", ex);
                }
            }
        }
    
    }
    

    DataPopulator.java

    public class DataPopulator {
    
        private static final Logger LOG = Logger.getLogger(DataPopulator.class);
    
        //SO CANNOT NEW AN INSTANCE
        private DataPopulator() {
        }
    
        //STATIC METHOD DEFINED HERE, COULD THIS POSE A SYNCHRONIZATION ISSUE FOR THE CALLING METHODS ???????
        public static Product populateProduct(ResultSet rs) throws SQLException {
            String productId = GenericUtils.nullToEmptyString(rs.getString("PRODUCT_ID"));
            String name = GenericUtils.nullToEmptyString(rs.getString("NAME"));
            String image = GenericUtils.nullToEmptyString(rs.getString("IMAGE"));
            String listPrice = GenericUtils.nullToEmptyString(rs.getString("LIST_PRICE"));
    
            Product product = new Product(new Integer(productId), name, image, new BigDecimal(listPrice));
            LOG.debug("product = " + product);
    
            return product;
        }
    
    }
    

3 个答案:

答案 0 :(得分:3)

您的代码是线程安全的。

原因和线程安全的关键是你的(静态)方法不维护状态。即你的方法只使用局部变量(而不是字段)。

方法是否静态无关紧要。

答案 1 :(得分:1)

  

假设存在同步问题;这些方法是否应该是非静态的,并且类可以立即生成,以便我们可以使用new运算符

创建实例

这不会有帮助。多个线程可以使用单个对象随意使用静态方法,并且您将遇到同步问题。

  

OR是否应在非线程安全代码周围放置同步块?

是的,这是安全的方式。同步块内的任何代码都保证在任何给定时间内最多有一个线程。

查看代码,我看不到许多可能在线程之间共享的数据结构。假设你有像

这样的东西
public final class QueryExecutor {
    int numQueries = 0;

    public void doQuery() {
        numQueries++;
    }
}

然后你遇到了麻烦,因为4个线程可能同时执行doQuery,所以你有4个线程修改numQueries的值 - 这是个大问题。

但是对于您的代码,唯一的共享类字段是日志记录类,它将内置自己的线程安全同步 - 因此您提供的代码看起来很好。

答案 2 :(得分:1)

代码中没有状态(例如,没有可变成员变量或字段),因此Java同步无关紧要。

另据我所知,没有数据库创建,更新或删除,因此也没有问题。

确实有一些可疑的做法(例如,数据库连接对象的非管理,一些变量的广泛范围,更不用说静态),但没有任何错误。

至于如何测试或确定线程安全性,您可能会比使用两个不同的浏览器并行手动操作您的站点更糟糕。或者创建一个shell脚本,使用curl执行自动HTTP请求。或者创建一个WebDriver测试,在各种真实浏览器中运行多个会话,并检查所有场景下预期的产品是否可见......