【问题标题】:how to make sure int is assigned a unique id for that thread如何确保为该线程分配了一个唯一的 id
【发布时间】:2013-02-07 22:49:38
【问题描述】:

我需要插入有两列的数据库-

ID      PrimaryKey String
ACCOUNT String

这意味着每个线程都应该始终使用唯一的 ID,我还需要在 Account 列中存储相同的 ID。所以假设如果ID is 1 那么在数据库中它应该被存储为

ID  Account
1   SomeString+1
2   SomeString+2
3   SomeString+3
....
..

100 SomeString+100

我总是在帐户列中将该用户 ID 与该字符串连接起来。

下面是我的多线程代码,它将产生多个线程——每个线程每次都会获得一个新的唯一 ID,因为我为此使用了 AtomicInteger。它会将ID 插入到ID column 并将ID 附加到Account

但不知何故,在我下面的程序中,我在该数据库中看到的是-

ID Account
1  String+2
2  String+1
3  String+3

这是不对的。应该是这样的-

ID Account
1  String+1
2  String+2
3  String+3

下面是代码

 public static void main(String[] args) {

        final int noOfThreads = 4;
        final int noOfTasks = 10;

        final AtomicInteger id = new AtomicInteger(1);

        ExecutorService service = Executors.newFixedThreadPool(noOfThreads);

        for (int i = 0; i < noOfTasks * noOfThreads; i++) {
            service.submit(new Task(id));
        }
    }


class Task implements Runnable {

    private final AtomicInteger id;
    private volatile int userId;

    public Task(AtomicInteger id) {
        this.id = id;
    }


    @Override
    public void run() {

        dbConnection = getDBConnection();

        preparedStatement = dbConnection.prepareStatement(Constants.INSERT_ORACLE_SQL);

        userId = id.getAndIncrement();

        preparedStatement.setString(1, String.valueOf(userId));
        preparedStatement.setString(2, Constants.getaAccount(userId));

        preparedStatement.executeUpdate();
    }  
}

下面是我的Constants class,我已将其设为不可变。

public final class Constants {

    public static String A_ACCOUNT;

    public final static String INSERT_ORACLE_SQL = "INSERT INTO XMP_TEST"
        + "("
        + "ID, A_ACCOUNT) VALUES"
        + "(?, ?)";



    public static String getaAccount(int userId) {      
        A_ACCOUNT = "{\"lv\":[{\"v\":{\"userId\":"+userId+"},\"cn\":1}]}";

        return A_ACCOUNT;
    }


}

谁能告诉我我在这里做错了什么?我相信这是因为线程安全问题而发生的。我猜有多个线程修改了userID 整数,这就是它被错误地写入数据库的原因。

我该如何解决这个问题?

【问题讨论】:

  • 你确定输出不是“2 String+2”然后是“1 String+1”吗?由于比赛条件,这会发生。由于 userId 没有改变,因此您显示的内容似乎是不可能的。

标签: java multithreading thread-safety atomic


【解决方案1】:

我看到的主要问题不是Task.userId,而是Constants.A_ACCOUNT:如果两个单独的线程同时调用getaAccount,那么它们都会设置Constants.A_ACCOUNT并且都读取它,所以它们最终可能都具有相同的价值,或者各自具有彼此的价值,或者诸如此类。要解决此问题,您可以使用局部变量而不是静态字段:

    public static String getaAccount(int userId) {      
        final String ret = "{\"lv\":[{\"v\":{\"userId\":"+userId+"},\"cn\":1}]}";

        return ret;
    }

或者只是省去变量:

    public static String getaAccount(int userId) {      
        return "{\"lv\":[{\"v\":{\"userId\":"+userId+"},\"cn\":1}]}";
    }

(你说你已经使Constants 不可变,但这不是真的。ConstantsInstances 将是不可变的,因为它们根本没有字段;但是 Constants它本身有一个可公开修改的字段,因此它非常可变!)

更一般地说,您不应该将字段用于仅在特定方法中需要的临时值,并且只能在一次调用期间使用。即使不是同步问题,也是维护问题。例如Task 不需要volatile int userIduserId 应该只是其 run 方法中的局部变量。

另外,我建议将您的AtomicInteger 包装在它自己的类中,IncrementingCounter 或其他东西,它只提供一种方法,称为(比如)getNewId。那么getNewId 将是唯一需要处理线程间协调的类。所有其他类都可以通过常规技术(不变性、仅存在于单个线程中等)成为线程安全的。

【讨论】:

  • aaahh... 我该怎么做。谢谢你的解释。
【解决方案2】:

您正在读取和修改一个静态变量,而无需从多个线程进行任何同步:A_ACCOUNT。只需将其设置为 getaacount() 中的局部变量,一切都会按预期工作。

【讨论】:

    【解决方案3】:

    考虑到数字的位数也是字符串,这个问题多年前就解决了:

    • 向父表添加自动增量列
    • 现在插入父记录将为您提供唯一编号
    • 您没有说您使用的是哪个数据库,但每个数据库都有一种方法可以检索刚刚插入的自动增量的值,因此请检索该值并将其用作您的唯一值。

    如果您的键无法更改数据类型,请将自动增量值复制到您的字符串列中。数字无关紧要 - 它仍然是唯一的。

    【讨论】:

    • 回复:“你没有说你使用的是哪个数据库”:没错,但显然是 Oracle。或者至少,我真的希望如此。 (所以,(s)他会使用一个序列而不是一个自动递增的列。但是,同样的想法。+1)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-06
    • 2022-07-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-19
    相关资源
    最近更新 更多