两层 if 防止同步问题

最近在阅读 weblech 的源代码的时候,发现一个写法有点意思。

public void saveCheckpoint()
    {
        SpiderConfig config = SpiderConfigInit.getSpiderConfigInit().getSingleSpiderConfig();
        long intervalMillis = 1000 * config.getCheckpointInterval();
        if(System.currentTimeMillis() - lastCheckpoint > intervalMillis)
        {
            synchronized(saveObj)
            {
                if(System.currentTimeMillis() - lastCheckpoint > intervalMillis) 
                {
                    writeCheckpoint(saveObj,saveFileName);
                    lastCheckpoint = System.currentTimeMillis();
                }
            }
        }
    }

这个函数位于一个线程类的内部,用来完成定时存档工作。它有两层 if 结构,中间有一个同步的操作。

隐约记得之前在《UNIX 环境高级编程》一书中看到过类似的用法,所以这里很快就知道了为什么这么写。

分析的思路如下:

假设结构为 if{synchronized}的结构,则可能在两个线程都进入if后,只有一个线程能进入锁并进行处理,而这个线程可能做了某些事,使得if条件不成立,但这时另一个线程已经通过了if,但这是编程人员在逻辑上不允许的,因为程序员应该保证每一步所产生的影响在可控范围内,否则,程序的逻辑则是模糊的,其运行的结果是无法预计的。所以,不可以为这个逻辑结构,除非你确定两个线程的操作对于再次进入 if 是没有影响的。


再假设,当结构为synchronized{if}的结构时会怎样呢?这其实很明显,如果一开始就同步,这会让不满足条件的线程耗费不必要的时间花在等待锁上,这样程序的效率就低了一点,所以要先判断它是否满足条件。


综上,所以结构是 if {synchronized if {}} 的结构。

我们再从正面来分析一下,这样写的好处。当存在多个线程会调用这个函数时,就会有线程进入最里层的 if ,记为 A,而还有线程卡在最外层的 if  的外面进不来。当A写存档之后,此时,正确的逻辑应该是其它的线程在时间间隔内不再需要写存档。所以,它会更改 lastCheckpoint 这个值,当卡在最外面的线程获得锁时,它又被卡在了最里面一个 if 的外面,它会直接返回,正好确实不需要写存档了。

### Java 实现两个数据库之间的数据同步 #### 使用 JDBC 进行数据库连接和操作 为了实现 MySQL 数据库 A 和 B 之间的数据同步,可以利用 JDBC API 来建立与这两个数据库的连接并执行相应的 CRUD 操作。由于只能调用中台提供的接口而不能直接书写 SQL 查询语句,在此情况下,可以通过封装好的方法间接完成这些功能。 对于每日定时任务的需求,可以在应用程序内部设置调度器(如 Quartz),或者借助外部工具和服务(例如 Linux 的 cron 或 Windows Task Scheduler)。这将确保每天固定时间触发一次完整的同步过程[^1]。 ```java // 建立数据库连接 Connection connA = DriverManager.getConnection(urlA, userA, passwordA); Connection connB = DriverManager.getConnection(urlB, userB, passwordB); try { // 设置事务隔离级别以防止脏读等问题 connA.setAutoCommit(false); connB.setAutoCommit(false); // 执行必要的业务逻辑... } catch (SQLException e) { try { if (!connA.getAutoCommit()) connA.rollback(); if (!connB.getAutoCommit()) connB.rollback(); } catch (SQLException ex) { throw new RuntimeException(ex.getMessage()); } } finally { try {if(connA != null && !connA.isClosed()) connA.close();}catch(SQLException ignored){} try {if(connB != null && !connB.isClosed()) connB.close();}catch(SQLException ignored){} } ``` #### 中台接口调用来代替SQL命令 考虑到特定环境下的约束——即不允许编写原始 SQL ——则应依赖于预先定义好并与服务端交互的服务层函数来进行增删改查等动作。通常这类接口会提供诸如 `create()`, `read()`, `update()` 及 `delete()` 方法用于处理记录级别的变更请求;同时也可能支持批量上传/下载多条目以便提高效率[^2]。 #### 利用 NiFi 流程简化复杂场景下ETL工作流构建 当面对更复杂的 ETL (Extract Transform Load)需求时,Apache NiFi 提供了一种图形化的方式去设计整个流水线,并内置了许多实用组件帮助开发者快速搭建起稳定可靠的数据迁移方案。比如上述提到过的 ExecuteSQL、ConvertAvroToJSON 等模块可以帮助我们轻松应对不同格式间转换以及跨平台传输所带来的挑战[^3]。 #### 示例代码展示基本框架结构 下面给出一段基于给定包路径 (`cn.gtmc.schedule.app`) 下面的一个简单例子,它实现了从源表向目标表复制员工信息的功能: ```java package cn.gtmc.schedule.app; import cn.gtmc.schedule.domain.Employee; import java.sql.*; import java.util.ArrayList; import java.util.List; public class EmployeeSync { private static final String URL_SOURCE_DB = "jdbc:mysql://localhost/source_db"; private static final String USER_SOURCE_DB = "root"; private static final String PASSWORD_SOURCE_DB = ""; private static final String URL_TARGET_DB = "jdbc:mysql://localhost/target_db"; private static final String USER_TARGET_DB = "root"; private static final String PASSWORD_TARGET_DB = ""; public void syncEmployees() throws SQLException { List<Employee> employeesFromSourceDB = fetchAllEmployees(URL_SOURCE_DB, USER_SOURCE_DB, PASSWORD_SOURCE_DB); insertOrUpdateIntoTargetDb(employeesFromSourceDB, URL_TARGET_DB, USER_TARGET_DB, PASSWORD_TARGET_DB); } private List<Employee> fetchAllEmployees(String url, String username, String pwd) throws SQLException { Connection connection = null; PreparedStatement statement = null; ResultSet resultSet = null; List<Employee> employeeList = new ArrayList<>(); try { Class.forName("com.mysql.cj.jdbc.Driver"); connection = DriverManager.getConnection(url, username, pwd); // Assuming there's a method to call the middleware service instead of writing raw SQL. CallableStatement callableStmt = connection.prepareCall("{call get_all_employees(?)}"); callableStmt.registerOutParameter(1, Types.CURSOR); callableStmt.execute(); resultSet = ((OracleCallableStatement)callableStmt).getCursor(1); while(resultSet.next()){ Employee emp = new Employee( resultSet.getString("id"), resultSet.getString("name"), ... ); employeeList.add(emp); } } finally { closeResources(connection, statement, resultSet); } return employeeList; } private void insertOrUpdateIntoTargetDb(List<Employee> emps, String targetUrl, String targetUser, String targetPwd){ // Similar logic here but using another stored procedure or web-service endpoint provided by your platform. } private void closeResources(Connection c, Statement s, ResultSet r){ if(r!=null){try{r.close();}catch(Exception ignore){}} if(s!=null){try{s.close();}catch(Exception ignore){}} if(c!=null){try{c.close();}catch(Exception ignore){}} } } class OracleCallableStatement extends CallableStatement{ // This is just an example placeholder. In real-world applications you would use actual implementations according to what database type and driver you are working with. public ResultSet getCursor(int parameterIndex)throws SQLException{return null;} } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值