【问题标题】:How to identify non thread-safe code in a multi-threaded environment?如何在多线程环境中识别非线程安全代码?
【发布时间】:2016-01-08 16:51:14
【问题描述】:

我已经设计并实现了一个基于传统 MVC 模型 1 架构的简单网络商店,使用纯 JSP 和 JavaBeans(是的,我仍然在我的宠物项目中使用该遗留技术;))。

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

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

请指导。

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;
    }

}

【问题讨论】:

标签: java multithreading design-patterns


【解决方案1】:

您的代码是线程安全的。

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

方法是否静态无关紧要。

【讨论】:

  • 我们如何知道protected DataSource 是线程安全的,尤其是对dataSource.getConnection() 的调用?
  • @jaco 强调你的代码。我们不知道 DataSource 是否是线程安全的,但我们相信我们使用的库是负责任地构建的。在实践中,您可以依赖大多数持久性库是线程安全的,因为它是正确行为所必需的(请参阅ACID)
【解决方案2】:

假设存在同步问题;是否应该将这些方法设为非静态且类可实例化,以便我们可以使用 new 运算符创建实例

这无济于事。多个线程可以随心所欲地使用单个对象,就像使用静态方法一样,您会遇到同步问题。

或者是否应该在非线程安全代码周围放置一个同步块?

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

查看您的代码,我没有看到许多可能在线程之间共享的数据结构。假设你有类似的东西

public final class QueryExecutor {
    int numQueries = 0;

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

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

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

【讨论】:

    【解决方案3】:

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

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

    肯定有一些可疑的做法(例如,数据库 Connection 对象的非管理,一些变量的广泛范围,更不用说静态),但没有错。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-17
      • 1970-01-01
      • 1970-01-01
      • 2016-01-23
      • 2013-12-23
      • 1970-01-01
      相关资源
      最近更新 更多