【问题标题】:When a thread is not in (Runnable) state while active当线程在活动时不处于(可运行)状态时
【发布时间】:2017-03-20 13:26:11
【问题描述】:

我希望你们一切都好。所以来到这个问题。我有这部分代码:

private static ArrayList<String> primelist = new ArrayList<>();
static void addToList(String list_elm) {
    primelist.add(list_elm);
}

基本上这个列表被我在完整代码(如下)中创建的多个线程同时访问,这些线程负责进行一些计算并返回结果,然后将其添加到这个primelist通过调用方法addToList(String list_elm)。

但是,在所有线程都被终止(即:完成它们的工作)之后,primelist 中存在一些空值。所以经过一些研究,事实证明 ArrayList 不是线程安全的类,因此它的方法也不是。我想问以下(也许很深)问题:

线程在执行一行代码时是否会被放入(等待//定时等待),也就是说它正在调用方法addToList(String list_elm)并且它到达primelist.add(list_elm);的行但同时添加它刚刚停止的元素!

如果不能,请澄清我对(尤其是)ArrayList 案例的困惑。 ((基本上是怎么回事?^^))

完整代码:

    import java.util.ArrayList;    
    import java.util.Iterator;

    public class CreatingAThreadThree
    {
    private static ArrayList<String> primelist = new ArrayList<>();
    static void addToList(String list_elm)
    {
        primelist.add(list_elm);
    }
    static ArrayList<String> getListReference(){
        return primelist;
    } 
    public static void main(String[] args)
    {
        for(long x = 6223372036854775899L; x<=(6223372036854775999L); x+=2)
        {
            new Thread (new MyCalcRunnable(x)).start();             
        }
        for(long x = 9223372036854774703L; x<=9223372036854774789L; x+=2 )
        {
            new MyCalcThread(x, "myChildThread"+x);             
        }

        Thread mainThread = Thread.currentThread();
        int spinner =0;
        char animation = ' ';
        System.out.println("Total number of active threads: " + Thread.activeCount());
        System.out.print("Calculating primes: ");
        while(Thread.activeCount()   >1)
        {
            spinner ++;
            switch(spinner)
            {
            case 1:
                animation = '|';
                break;
            case 2:
                animation = '/';
                break;
            case 3:
                animation = '-';
                break;
            case 4:
                animation = '\\';
                spinner = 0;
                break;
            }
            System.out.print("\b" + animation);
            try
            {
                Thread.sleep(200);
            }catch(InterruptedException ex)
            {
            }           
        }
        System.out.println("Total number of active threads: " + Thread.activeCount());
        System.out.println("Results List:");
        Iterator<?> iterator = (getListReference().iterator());
        while(iterator.hasNext())
        {
            System.out.println(iterator.next());
        }
    }
    }

    class MyCalcThread extends Thread
    {
    private long numberToFactor = 0;
    MyCalcThread(long numberToFactor, String name)
    {
        super(name);
        this.numberToFactor = numberToFactor;
        start();
    }

    @Override
    public void run()
    {
        CreatingAThreadThree.addToList(new PrimeStuff().isItPrime(this.numberToFactor));
    }
    }

    class MyCalcRunnable implements Runnable
    {
    private long numberToFactor = 0;
    MyCalcRunnable(long numberToFactor)
    {
        this.numberToFactor = numberToFactor;   
    }
    @Override
    public void run()
    {
    CreatingAThreadThree.addToList(new PrimeStuff().isItPrime(this.numberToFactor));
    }
    }

    class PrimeStuff
    {
    String isItPrime(long numberToFactor)
    {
        if(numberToFactor % 2 == 0)         
            return (numberToFactor +"is Not prime....divisible by 2");

        long squareRoot = (long)(Math.sqrt(numberToFactor));
        for(long i=3; i<squareRoot; i++)
        {
            if(numberToFactor % i == 0)
            {
                return (numberToFactor +"is Not prime....first divisible by " +                  i);    
            }
        }
        return (numberToFactor + " is Prime!!");
    }
    }

【问题讨论】:

  • 这里有一点需要注意:您想了解 Java 编码风格约定。例如,您不要在普通变量名中使用“_”;这些仅适用于 SOME_CONSTANT。
  • 据我所知,当所有字母都是大写时,“_”按约定用于常量,而我的代码中并非如此。
  • 我的意思是:_ 仅用于常量。例如,它不像您的 list_elem 那样使用。
  • @Xinchao 在 question 中重构代码是一个真的 坏主意,因为它可能会混淆 OP 和答案以及未来的读者。如果您想要一个规范的MCVE,我建议您提出一个新问题作为一个较小的邪恶(其中一个问题将是重复的。)

标签: arraylist thread-safety copyonwritearraylist


【解决方案1】:

您关注的是错误的问题;含义:花时间修复损坏的代码。

当您有多个线程访问同一个共享的、不受保护的数据时;各种事情都可能发生。

另外:仅仅改变你正在使用的列表的类型可能是不够的。你看,CopyOnWrite 列表保证了单个操作的“线程安全”。但是当你有类似的东西时

if (someList.size() > 1) {
  do something with your list

仍然不安全;即使使用 CopyOnWrite 列表 - 因为列表上有两个调用;并且列表可能会在第一次和第二次调用之间发生变化;当其他线程同时更改列表时。

长话短说:一种解决方案是在您的方法上使用synchronized,而您确实不想希望并行运行。

换句话说:添加您需要的保护;而不是通过理解线程状态模型的细节来迷惑自己——那部分不会帮助你编写正确的代码。

鉴于您的评论:您尝试在非常“低”的级别上解决此问题。您认为您必须了解线程状态和等待条件等才能得出“好的”解决方案。但这不是一种有效的方法。特别是当你是新手并学习这些东西时。

首先,您应该担心找到正确的解决方案。然后你可以继续前进并增强它;例如通过做不同的实验。从这个意义上说,您应该明白:在真正的多线程中,我们通常会尝试从这些低级细节中抽象。相反,我们甚至引入了附加层,例如Executors。

我想告诉你的是:查看底层细节很可能对你没有帮助,但在这一点上会让你负担过重。

【讨论】:

  • 谢谢,感谢您在这方面为我提供的帮助。但是,您看到我猜想在该代码中唯一的问题是调用方法以将元素添加到 ArrayList。至于其他操作,它们都必须在一个线程处于活动状态后执行,这基本上是主线程(这是根据我目前的知识的限制),除非这里有更多要说的。最后我想说的是,我想了解线程模型,以便预测我必须在哪里进行保护,以获得更好的更清洁和更安全(按需(不多不少))代码。
  • 再次感谢您帮助我。我之前实际上已经研究过这些基本主题中的大部分......,现在我正在尝试回顾并深入研究这个主题......至于上面的代码......,这不是一个完美的,我确实意识到如何解决这个问题..,但我认为更好地了解幕后发生的事情是个好主意。毕竟我绝对还是个新手^^,但是对于这些不合常规的命名变量我只是没有注意!..,谢谢你的好建议。
  • 对于绿色勾号给您带来的不便,请见谅..,我的电脑出了问题..,你绝对值得。但我想对你坦率地说..,我确实希望得到一个直接针对问题的答案..,稍后会为关于问题或提出问题的人的关心或个人意见加分。所以按照惯例,你的答案是迄今为止最好的^^。
【解决方案2】:

竞争条件可以发生在任何地方,不仅在您编写的代码中,而且在 ArrayList 的实现中。

即使以最简单的形式添加到 ArrayList 也将涉及以下操作:

  1. 读取size变量
  2. 将数据写入elementData[size]
  3. 增加size 变量

当添加操作增加尺寸大于elementData的长度时,也会发生尺寸调整:

  1. 创建一个 1.5 倍于当前elementData 的新数组
  2. 将elementData 中的值复制到新数组中
  3. 将新数组分配给elementData
  4. 将数据写入elementData[size]
  5. 增加size 变量

在您的情况下,如果您将 null 添加到列表中,则可能有两个线程正在尝试同时调整数组的大小。假设目前您的列表有 10 个元素。 elementData 的长度为 10。因此添加第 11 个值将调整 elementData 的大小

  1. 线程 1 创建一个长度为 15 的新数组 X
  2. 线程 1 将数据从 elementData 复制到新数组中
  3. 线程 2 创建一个长度为 15 的新数组 Y
  4. 线程 2 将数据从 elementData 复制到新数组中
  5. 线程 1 将 X 分配给 elementData
  6. 线程 1 将值写入elementData[10]
  7. 线程 1 将 size 递增到 11
  8. 线程 2 将 Y 分配给 elementData
  9. 线程 2 将值写入elementData[11]
  10. 线程 2 将 size 递增到 12
  11. elementData[10] 结果将为空

第 8 步发生的情况是线程 2 覆盖了线程 1 更改的 elementData,因此 elementData[10](由线程 1 写入)丢失。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-16
    • 1970-01-01
    • 2013-09-12
    相关资源
    最近更新 更多