由一个面试题引发的思考

前两天在网上看到一个程序员兄弟谈他的面试经,其中讲了他遇到的一个问题:

给了一张表 三个字段:
id name date
id为主键
现在要求用10个线程向这张表中插入10000条数据 
其中id不能是自增,请问该如何实现?能大致说一下思路么?

这样一个问题怎么处理?

是不是似曾相识呢?我们肯定在哪里见过。

说点题外话:为什么不用自增?这里面试官的意图是想考察线程安全方面的处理

  对大型的系统自增会存在以下问题:

  1) 比如做数据库优化,在大表做水平分表时,就不能使用自增Id,因为Insert的记录插入到哪个分表依分表规则判定决定,若是自增Id,各个分表中Id就会重复,在做查询、删除时就会有异常。

  2) 在对表进行高并发单记录插入时需要加入事物机制,否则会出现Id重复的问题。

  3) 在业务上操作父、子表(即关联表)插入时,需要在插入数据库之前获取max(id)用于标识父表和子表关系,若存在并发获取max(id)的情况,max(id)会同时被别的线程获取到。

  4)  可能面临特定的订单号等等需求。

网上查了下,大家推荐使用AtomicInteger类的addAndGet方法来实现唯一标示,这样可是实现“线程安全”。

然后,给出1.0的方案:10个线程,每个线程插入1000条数据,那么整体就是10000条数据了,满足要求

MyTread.java

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;

public class MyThread extends Thread{
    private static AtomicInteger IntId = new AtomicInteger(0);

    public static int getIntId()
    {
        return ((int)(IntId.addAndGet(1)));
    }
    public void run() {
        String url = "jdbc:mysql://127.0.0.1/bigdatatest";
        String name = "com.mysql.jdbc.Driver";
        String user = "root";
        String password = "";
        Connection conn = null;
        try {
            Class.forName(name);
            conn = DriverManager.getConnection(url, user, password);//获取连接
            conn.setAutoCommit(false);//关闭自动提交,不然conn.commit()运行到这句会报错
        } catch (ClassNotFoundException e1) {
            e1.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        // 开始时间
        Long begin = new Date().getTime();
        // sql前缀
        String prefix = "INSERT INTO test (id,name,data) VALUES ";
        try {
            // 保存sql后缀
            StringBuffer suffix = new StringBuffer();
            // 设置事务为非自动提交
            conn.setAutoCommit(false);
            // 比起st,pst会更好些
            PreparedStatement  pst = (PreparedStatement) conn.prepareStatement("");//准备执行语句
            // 外层循环,总提交事务次数
            for (int i = 1; i <= 1000; i++) {
                suffix = new StringBuffer();
                    String uuid= UUID.randomUUID().toString();
                    int intId = getIntId();
                    // 构建SQL后缀
                    suffix.append("('"+intId+"','" +uuid+"','"+uuid+"'),");
                // 构建完整SQL
                String sql = prefix + suffix.substring(0, suffix.length() - 1);
                // 添加执行SQL
                pst.addBatch(sql);
                // 执行操作
                pst.executeBatch();
                // 提交事务
                conn.commit();
                // 清空上一次添加的数据
                suffix = new StringBuffer();
            }
            // 头等连接
            pst.close();
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        // 结束时间
        Long end = new Date().getTime();
        // 耗时
        System.out.println("1000条数据插入花费时间 : " + (end - begin) / 1000 + " s"+"  插入完成");
    }
}
View Code

测试类:Main.java

public class Main {

    public static void main(String[] args) {
        for (int i = 1; i <=10; i++) {
            new MyThread().start();
        }
}
View Code

运行结果:成功插入了,AtomicInteger类的addAndGet方法实现乐唯一标示,但是速度较慢。

看看程序好像可以优化下,上面的代码是提交了1000次,这样效率比较慢,可以改进成分成10组,先拼装然后提交:

MyThread.java

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;

public class MyThread extends Thread{
    private static AtomicInteger IntId = new AtomicInteger(0);

    public static int getIntId()
    {
        return ((int)(IntId.addAndGet(1)));
    }
    public void run() {
        String url = "jdbc:mysql://127.0.0.1/bigdatatest";
        String name = "com.mysql.jdbc.Driver";
        String user = "root";
        String password = "";
        Connection conn = null;
        try {
            Class.forName(name);
            conn = DriverManager.getConnection(url, user, password);//获取连接
            conn.setAutoCommit(false);//关闭自动提交,不然conn.commit()运行到这句会报错
        } catch (ClassNotFoundException e1) {
            e1.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        // 开始时间
        Long begin = new Date().getTime();
        // sql前缀
        String prefix = "INSERT INTO test (id,name,data) VALUES ";
        try {
            // 保存sql后缀
            StringBuffer suffix = new StringBuffer();
            // 设置事务为非自动提交
            conn.setAutoCommit(false);
            // 比起st,pst会更好些
            PreparedStatement  pst = (PreparedStatement) conn.prepareStatement("");//准备执行语句
            // 外层循环,总提交事务次数
            for (int i = 1; i <= 10; i++) {
                suffix = new StringBuffer();
                // 第j次提交步长
                for (int j = 1; j <= 100; j++) {
                    String uuid= UUID.randomUUID().toString();
                    int intId = getIntId();
                    // 构建SQL后缀
                    suffix.append("('"+intId+"','" +uuid+"','"+uuid+"'),");
                }
                // 构建完整SQL
                String sql = prefix + suffix.substring(0, suffix.length() - 1);
                // 添加执行SQL
                pst.addBatch(sql);
                // 执行操作
                pst.executeBatch();
                // 提交事务
                conn.commit();
                // 清空上一次添加的数据
                suffix = new StringBuffer();
            }
            // 头等连接
            pst.close();
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        // 结束时间
        Long end = new Date().getTime();
        // 耗时
        System.out.println("1000条数据插入花费时间 : " + (end - begin) / 1000 + " s"+"  插入完成");
    }
}
View Code

测试代码:

Main.java

public class Main {
    public static void main(String[] args) {
        for (int i = 1; i <=10; i++) {
            new MyThread().start();
        }
    }
}
View Code

结果:速度比第一次快了很多:

关于这个面试题,大家如果有好的方法,请尽情提供,大家一起学习下!!

另外关于高并发情况下的线程安全,大家有什么看法?望一起讨论!



原文地址:https://www.cnblogs.com/miketwais/p/Highly_concurrent.html