如何使用 Java 在多线程环境中测试某些东西 [重复]

Posted

技术标签:

【中文标题】如何使用 Java 在多线程环境中测试某些东西 [重复]【英文标题】:How to test something in Multi-Threaded Environment using Java [duplicate] 【发布时间】:2016-01-28 21:28:25 【问题描述】: 如何在多线程环境中测试类似的东西。我知道它会失败,因为这段代码不是线程安全的。我只想知道如何证明?创建一堆线程并尝试添加这些不同的线程?出于测试目的,此代码故意没有正确编写!!!

公共类 Response_Unit_Manager

private static HashMap<String, Response_Unit> Response_Unit_DB =
        new HashMap<> ();

/**
 * 
 * This subprogram adds a new Response_Unit to the data store.  The
 * new response unit must be valid Response_Unit object and it's ID must be 
 * unique (i.e., must not already exist in the data store.
 * 
 * Exceptions Thrown:  Null_Object_Exception
 */
public static void Add_Response_Unit (Response_Unit New_Unit) 
        throws Null_Object_Exception, Duplicate_Item_Exception 
    String Unit_ID = New_Unit.Unit_ID ();

    if (New_Unit == null)
        throw new Null_Object_Exception ();

    else if (Response_Unit_Exists (Unit_ID))
        throw new Duplicate_Item_Exception (Unit_ID);

    else
        Response_Unit_DB.put (Unit_ID, New_Unit);
 //end Add_Response_Unit

【问题讨论】:

您希望它失败吗?交付它。 @MartinJames 哈哈 .. 我们以前从未使用过线程。我们只是需要一些帮助来证明这段代码不是线程安全的。如果您能在这里为我们提供帮助,我将不胜感激 :) 你可能会幸运地看到失败,但不失败的代码并不意味着它是线程安全的代码。检查线程安全性的唯一自动化方法是使用一些静态分析工具,这些工具可让您在方法/类上添加注释并扫描潜在问题。 @mvd 感谢您的回复。但不幸的是,我们所拥有的只是一个带有 main 方法的测试器类。您认为我们可以仅使用 main 方法来证明我们的观点吗?再次感谢... 【参考方案1】:

运行测试时您可能会很幸运并看到失败,但未失败的代码并不意味着它是线程安全的代码。检查线程安全性的唯一自动化方法是使用一些静态分析工具,这些工具可让您在方法/类上添加注释并扫描潜在问题。例如,我知道 FindBugs 支持一些注释并基于它们进行并发检查。您应该能够将其应用于您的单个 Tester 类。业界在这个话题上仍有很大的改进空间,但这里有一些当前的例子:

http://robertfeldt.net/publications/grahn_2010_comparing_static_analysis_tools_for_concurrency_bugs.pdf http://homepages.inf.ed.ac.uk/dts/students/spathoulas/spathoulas.pdf

【讨论】:

【参考方案2】:

正如其他人所指出的,您不能编写保证失败的测试,因为线程调度可能“刚刚解决”,但您可以编写通过概率非常低的测试如果存在线程安全问题。例如,您的代码尝试禁止数据库中的重复项,但由于线程安全问题,它不能这样做。因此,产生大量线程,让它们都等待CountdownLatch 或其他东西以最大化触发比赛的机会,然后让它们都尝试插入相同的项目。最后,您可以检查 (a) 除了一个线程之外的所有线程都看到了 Duplicate_Item_Exception 和 (b) Response_Unit_DB 仅包含一个项目。对于这些类型的测试,您还可以运行多次(在同一个测试中),以最大限度地提高触发问题的机会。

这是一个例子:

@Test
public void testIsThreadSafe() 
   final int NUM_ITERATIONS = 100;
   for(int i = 0; i < NUM_ITERATIONS; ++i) 
       oneIsThreaSafeTest();
   


public void oneIsThreadSafeTest() 
    final int NUM_THREADS = 1000;
    final int UNIT_ID = 1;
    final Response_Unit_Manager manager = new Response_Unit_Manager();
    ExecutorService exec = Executors.newFixedThreadPool(NUM_THREADS);
    CountdownLatch allThreadsWaitOnThis = new CountdownLatch(1);
    AtomicInteger numThreadsSawException = new AtomicInteger(0);

    for (int i = 0; i < NUM_THREADS; ++i) 
        // this is a Java 8 Lambda, if using Java 7 or less you'd use a
        // class that implements Runnable
        exec.submit(() -> 
            allThreadsWaitOnThis.await();
            // making some assumptions here about how you construct
            // a Response_Unit
            Response_Unit unit = new Response_Unit(UNIT_ID);
            try 
                manager.Add_Response_Unit(unit);
             catch (Duplicate_Item_Exception e) 
              numThreadsSawException.incrementAndGet();
            
        );

        // release all the threads
        allThreadsWaitOnThis.countdown();

        // wait for them all to finish
        exec.shutdown();
        exec.awaitTermination(10, TimeUnits.MINUTES);

       assertThat(numThreadsSawException.get()).isEqualTo(NUM_THREADS - 1);
  

您可以针对其他潜在的线程安全问题构建类似的测试。

【讨论】:

【参考方案3】:

查找测试错误的最简单方法(例如您的课程中包含的错误)是使用 Testrunner,例如以下:

package com.anarsoft.mit;


import java.util.concurrent.atomic.AtomicInteger;


public class Test_Response_Unit_Manager  implements Runnable 


private final AtomicInteger threadCount = new AtomicInteger();

public void test() throws Exception

    for(int i = 0; i < 2 ;i++)
    
        Thread thread = new Thread(this, "Thread " + i);
        this.threadCount.incrementAndGet();
        thread.start();
    

    while( this.threadCount.get() > 0 )
    
        Thread.sleep(1000);
    

    Thread.sleep(10 * 1000);



public void run()

    exec();
    threadCount.decrementAndGet();



protected  void exec()

    Response_Unit_Manager.Add_Response_Unit(new Response_Unit(Thread.currentThread().getId()));


public static void main(String[] args) throws Exception

    (new Test_Response_Unit_Manager()).test();


并使用动态竞争条件检测工具,如 http://vmlens.com,一个轻量级竞争条件检测器。这将显示以下竞争条件:

还有导致错误的堆栈跟踪。左边是写,右边是读。

http://vmlens.com 与 eclipse 一起使用,所以它取决于你使用的 ide,如果它对你有用

【讨论】:

以上是关于如何使用 Java 在多线程环境中测试某些东西 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

如何保证单例模式在多线程中的线程安全性

如何保护可能在多线程或异步环境中使用的资源?

如何在多线程环境中使用嵌入式 MySQL?

jsch的sftp在多线程下的问题及处理办法

如何在多线程环境中有效地使用RestTemplate?

Java 中线程安全问题