什么是“非阻塞”并发,它与普通并发有什么不同?

时间:2010-05-13 03:14:35

标签: java concurrency

  1. 什么是“非阻塞”并发,它与使用线程的普通并发有什么不同?为什么我们不在所有需要并发的场景中使用非阻塞并发?是否存在使用非阻塞并发的开销?
  2. 我听说Java中提供了非阻塞并发。我们是否应该使用此功能?
  3. 使用其中一种方法与集合有区别或优势吗?有什么权衡取舍?
  4. Q3的例子:

    class List   
    {  
        private final ArrayList<String> list = new ArrayList<String>();
    
        void add(String newValue) 
        {
            synchronized (list)
            {
                list.add(newValue);
            }
        }
    }  
    

    VS

    private final ArrayList<String> list = Collections.synchronizedList(); 
    

    问题更多来自学习/理解的观点。谢谢你的关注。

8 个答案:

答案 0 :(得分:16)

  

什么是非阻塞并发和       它有什么不同。

正式:

  

在计算机科学中,非阻塞   同步确保线程   竞争共享资源不会   无限期地执行   因互斥而推迟。一个   非阻塞算法是无锁定的   系统范围内有保证   进展;等等,如果还有   保证每线程进度。   (维基百科)

非正式:非阻塞与阻塞最有利的特性之一是,线程不必被OS暂停/唤醒。这样的开销可能达到1毫秒到几十毫秒,因此删除它可以获得很大的性能提升。在java中,它还意味着您可以选择使用非公平锁定,这可以比公平锁定具有更多的系统吞吐量。

  

我听说这是可用的       在Java中。有什么特别的吗?       场景我们应该使用这个功能

是的,来自Java5。实际上,在Java中,您应该尽可能地尝试使用java.util.concurrent来满足您的需求(这恰好使用了非阻塞并发,但在大多数情况下您不必明确担心)。只有在没有其他选项的情况下,才应使用synchronized wrappers(.synchronizedList()等)或manual synchronize关键字。这样,大部分时间你都可以使用更易于维护,性能更好的应用程序。

当存在大量争用时,非阻塞并发特别有利。当您需要阻塞(公平锁定,事件驱动的东西,具有最大长度的队列等)时,您不能使用它,但如果您不需要,非阻塞并发性往往在大多数情况下表现更好。

  

是否存在差异/优势       使用这些方法之一       采集。有什么权衡

两者都具有相同的行为(字节代码应该相等)。但我建议使用Collections.synchronized,因为它更短=更小的空间搞砸了!

答案 1 :(得分:10)

  

1)什么是非阻塞并发以及它是如何不同的。

当任务(线程)不会导致其他任务(线程)等待任务完成时,它是非阻塞的。

  

2)我听说这是Java版本。是否有任何特殊情况我们应该使用此功能

Java支持自己的多线程。您可以利用它同时运行多个任务。如果写得好并且实现得当,这可以在具有多个逻辑CPU的机器上运行时加速程序。首先,有java.lang.Runnablejava.lang.Thread作为低级并发实现。然后,java.util.concurrent API的风格具有高级并发性。

  

3)使用这些方法之一进行收集是否存在差异/优势。有什么权衡

我会使用Collections#synchronizedList(),不仅因为它更加强大和方便,而且因为Map不是List。当API已经提供设施时,没有必要尝试增加一个。


那就是Sun tutorial about Concurrency。我建议你自己完成它。

答案 2 :(得分:10)

  

什么是非阻塞并发?它与使用线程的普通并发有什么不同。

非阻塞并发是一种不同的方法,用于协调阻塞并发的线程之间的访问。有很多背景(理论)材料,但最简单的解释(似乎你正在寻找一个简单的,实际的答案),非阻塞并发不使用锁。 / p>

  

为什么我们不要在需要并发的所有场景中使用“非阻塞”并发。

我们这样做。我会告诉你一点。但对于每个并发问题,确实并不总是有效的非阻塞算法。

  

是否存在“非阻塞”

的任何开销

嗯,线程之间的任何类型的共享信息都会产生开销,这些信息一直到CPU的结构,特别是当你得到我们称之为“争用”的东西时,即同步多个尝试写入的线程同时到同一个内存位置。 但一般来说,在许多情况下,非阻塞比阻塞(基于锁定)的并发更快,尤其是在给定算法/数据结构的众所周知的,简单的无锁实现的所有情况下。这些是Java提供的优秀解决方案。

  

我听说这可以用Java获得。

绝对。首先,java.util.concurrent.atomic中的所有类都提供了对共享变量的无锁维护。此外,名称以ConcurrentLinked或ConcurrentSkipList开头的java.util.concurrent中的所有类都提供了列表,映射和集合的无锁实现。

  

我们是否应该使用此功能。

您希望在所有情况下(在JDK 1.5之前)使用Collections.synchronizedlist时使用无锁队列和双端队列,因为它们在大多数情况下提供更好的性能。也就是说,只要多个线程同时修改集合,或者当一个线程正在修改集合而其他线程正在尝试读取它时,您就会使用它们。 请注意,非常流行的ConcurrentHashMap实际上在内部使用锁,但它比ConcurrentSkipListMap更受欢迎,因为我认为它在大多数情况下提供了更好的性能。但是,我认为Java 8将包含ConcurrentHashMap的无锁实现。

  

使用这些方法之一对集合是否有区别/优势。有什么权衡

嗯,在这个简短的例子中,它们完全相同。但请注意,当您有并发读取器和写入器时,必须同步读取和写入,Collections.synchronizedList()会这样做。 您可能希望尝试使用无锁的ConcurrentLinkedQueue作为替代方案。它可能会在某些情况下为您提供更好的性能。

一般说明

虽然并发性是一个非常重要的学习主题,但请记住,它也是一个非常棘手的主题,即使是非常有经验的开发人员也常常犯错误。更糟糕的是,只有当系统负载很重时,才会发现并发错误。因此,我总是建议尽可能多地使用现成的并发类和库,而不是自己推出。

答案 3 :(得分:4)

  

1]什么是非阻塞并发和   它有什么不同。

正如其他人所提到的,非阻塞是一种说无死锁的方式(意味着我们不应该有一个条件,当线程被阻塞时,进程完全停止,等待访问)。

'并发'的含义就是多个计算同时发生(同时发生)。

  

2]我听说这是可用的   在Java中。有什么特别的吗?   场景我们应该使用此功能

当多个线程同时访问相同资源很重要时,您希望使用非阻塞算法,但我们并不关心访问顺序或交错操作的可能后果(更多内容如下)

  

3]是否存在差异/优势   使用这些方法之一   采集。有什么权衡

使用synchronized(list)块可确保块内执行的所有操作都被视为原子操作。也就是说,只要我们只从同步(列表)块访问列表,列表的所有更新都将显示为在块中同时发生。

synchronizedList(或synchronizedMap)对象仅确保单个操作是线程安全的。这意味着两个插入不会同时发生。考虑以下循环:

for(int i=0; i < 4; i++){
    list.add(Integer.toString(i));
}

如果使用的列表是synchronizedList并且这个循环是在两个不同的线程上执行的,那么我们可能会在列表中找到{0,0,1,2,1,3,2,3},或者一些其他排列。

为什么呢?好吧,我们保证线程1将按顺序添加0-3,我们保证线程2相同,但我们无法保证它们将如何交错。

但是,如果我们将此列表包装在同步(列表)块中:

synchronized(list){
    for(int i=0; i < 4; i++){
        list.add(Integer.toString(i));
    }
}

我们保证来自线程1和线程2的插入不会交错,但它们将同时发生。我们的列表将包含{0,1,2,3,0,1,2,3}。另一个线程将阻塞,等待列表,直到第一个线程完成。我们无法保证哪个线程是第一个,但我们保证它会在另一个线程开始之前完成。

所以,一些权衡是:

  • 使用syncrhonizedList,您可以 插入而不明确使用 同步块。
  • syncrhonizedList可能会给你一个 因为你的错误的安全感 天真地相信连续 在一个线程上的操作 原子,只有个人 操作是原子的
  • 必须小心使用syncrhonized(list)块,因为我们可以创建死锁(更多信息如下)。

当两个(或更多)线程都在等待另一个线程持有的资源子集时,我们可以创建死锁。例如,如果您有两个列表:userList和movieList。

如果线程1首先获取对userList的锁定,则为movieList,但是线程2反向执行这些步骤(在userList之前获取对movieList的锁定),然后我们将自己打开以进行死锁。请考虑以下事件:

  1. 线程1锁定到userList
  2. 线程2获取对movieList的锁定
  3. 线程1尝试锁定movieList,等待线程2释放
  4. 线程2尝试锁定userList,等待线程1释放
  5. 两个线程都在等待另一个线程,两个线程都无法前进。这是一个阻塞的情况,由于两者都不会放弃它的资源,我们陷入僵局。

答案 4 :(得分:3)

1)非阻止同步表示已删除deadlocks的风险。没有一个线程会等待另一个线程“永远”保持锁定。

2)更多关于java中的非阻塞同步algorithms

答案 5 :(得分:2)

  1. 维基百科是任何计算机科学专业学生的理想资源。以下是有关非阻止同步的文章 - http://en.wikipedia.org/wiki/Non-blocking_synchronization

  2. 非阻塞同步可用于任何语言,它是程序员定义其多线程应用程序的方式。

  3. 由于获取锁定所需的时间,您只应在必要时使用锁定(即synchronized(list))。在Java中,Vector是一个线程安全的数据结构,与Java的ArrayList非常相似。

答案 6 :(得分:2)

1:什么是&#34;非阻塞&#34;并发性和它与使用线程的普通并发有什么不同?为什么我们不在所有需要并发的场景中使用非阻塞并发?是否存在使用非阻塞并发的开销?

非阻塞算法不使用特定的对象锁定方案来控制对内存的并发访问(同步和标准对象锁是使用对象/函数级别锁来减少Java中的并发访问问题的示例。相反,它们使用某种形式的低级别执行(在某种程度上)同步比较和交换内存位置的指令;如果失败,它只返回false并且不会输出错误,如果它有效则会成功并继续前进。通常,这是在循环直到它工作,因为只有很短的时间(希望),当它失败时,它只是循环几次,直到它可以设置它所需的内存。

并不总是使用它,因为从代码的角度来看,即使对于比标准Java同步而言相对简单的用例,它也要复杂得多。此外,对于大多数用途,与系统中的其他来源相比,锁定的性能影响是微不足道的。在大多数情况下,性能要求不够高,甚至无法保证这一点。

最后,随着JDK / JRE的发展,核心设计人员正在改进内部语言实现,以尝试在核心结构中结合实现这些目标的最有效方法。当你离开核心结构时,你会失去这些改进的自动实现,因为你使用的是较不标准的实现(例如jaxb / jibx; jaxb曾经严重低于jibx,但现在在大多数情况下如果不是更快,那么我和当你碰到java版本时,已经测试了java 7)。

如果查看下面的代码示例,您可以看到&#39;开销&#39;位置。它本身并不是真正的开销,但是由于循环,代码必须非常高效才能工作非锁定并且实际上比标准同步版本执行得更好。即使是轻微的修改,也可能导致代码从标准执行好几倍到代码差几倍(例如不需要在那里进行的对象实例化甚至是快速的条件检查;你还需要在这里讨论保存周期,所以成功和失败之间的差异非常小。)

2:我听说Java中可以使用非阻塞并发。我们是否应该使用此功能?

在我看来,你应该只使用它,如果你A)在你的生产系统中,在你的生产硬件上有一个经过验证的性能问题;和B)如果你能证明关键部分留下的唯一低效率是锁定相关的; C)您从利益相关方那里坚定地购买他们愿意使用非标准的可维护性较低的代码以换取性能改进,您必须D)在生产硬件上用数字证明,以确保它甚至可以提供帮助。 / p>

3:在集合中使用这些方法之一是否存在差异或优势?有什么权衡取舍?

优势在于性能,首先是权衡它是更专业的代码(许多开发人员不知道如何制作它;让新团队或新雇员更难以来速度快,请记住,软件的大部分成本都是劳动力,因此您必须观察通过设计决策强加的总体拥有成本),并且应该再次测试任何修改以确保构造实际上仍然存在快点。通常在需要这种系统的系统中,任何更改都需要一些性能或负载和吞吐量测试。如果你没有进行这些测试,那么我认为你几乎肯定不需要考虑这些方法,并且几乎肯定不会看到增加复杂性的任何价值(如果你把它全部付诸实践)工作正确)。

同样,我只需要重复所有标准警告以反对优化,因为许多这些参数与我作为设计使用的相同。这方面的许多缺点与任何优化相同,例如,每当您更改代码时,都必须确保您的“修复”。在一些仅用于提高性能的构造中,并没有引入低效率,并且如果修复是关键的并且它降低了性能,那么处理它(意味着重构整个部分以潜在地删除优化)。

以非常难以调试的方式实现这一点非常容易,所以如果你不必这样做(我只发现了一些你曾经想过的场景;并且我这些都是值得怀疑的,我宁愿不这样做也不要这样做。使用标准的东西,每个人都会更快乐!

讨论/代码

非阻塞或无锁并发避免使用特定对象锁来控制共享内存访问(如同步块或特定锁)。当代码部分是非锁定时,有一个性能优势;然而,CAS循环中的代码(如果这是你的方式,Java中还有其他方法)必须非常非常有效,否则最终会使你获得比你获得的更多性能。

与所有性能优化一样,对于大多数用例而言,额外的复杂性并不值得。使用标准结构编写干净的Java也可以比大多数优化更好(并且实际上允许您的组织在您离开后更轻松地维护软件)。在我看来,只有具有可靠性能问题的高性能部分才有意义,因为锁定是低效率的唯一来源。如果你肯定没有一个已知和量化的性能问题,我会避免使用任何这样的技术,直到你已经证明问题实际存在,因为锁定而不是代码效率的其他问题。一旦你有一个经过验证的基于锁定的性能问题,我会确保你有一些类型的指标来确保这种类型的设置实际上比你使用标准的Java并发更快地运行。

我为此使用CAS操作和Atomic变量系列所做的实现。在此用例中,此基本代码从未锁定或引发任何错误(从高吞吐量转换系统进行离线测试的随机采样输入和输出)。它基本上是这样的:

您有一些在线程之间共享的对象,并且这被声明为AtomicXXX或AtomicReference(对于您将使用AtomicReference版本运行的大多数非常重要的用例)。

当引用给定的值/对象时,从Atomic包装器中检索它,这将获得一个本地副本,您可以在其上执行一些修改。从这里你使用compareAndSwap作为while循环的条件来尝试从你的线程设置这个Atomic,如果失败则返回false而不是锁定。这将迭代直到它工作(此循环中的代码必须非常高效和简单)。

您可以查看CAS操作以查看它们是如何工作的,它基本上旨在作为单个指令集实现,并在最后进行比较,以查看该值是否是您尝试将其设置为的值。

如果compareAndSwap失败,则从Atomic包装器再次获取对象,再次执行任何修改,然后再次尝试比较和交换,直到它工作。没有特定的锁,你只是试图将对象重新设置回内存,如果它失败了,你只需在你的线程再次获得控制权时再试一次。

以下是针对带有List的简单案例的代码:

/* field declaration*/
//Note that I have an initialization block which ensures that the object in this
//reference is never null, this was required to remove null checks and ensure the CAS
//loop was efficient enough to improve performance in my use case
private AtomicReference<List<SampleRuleMessage>> specialSamplingRulesAtomic = new AtomicReference<List<SampleRuleMessage>>();


/*start of interesting code section*/

    List<SampleRuleMessage> list = specialSamplingRulesAtomic.get();
    list.add(message);
    while(!specialSamplingRulesAtomic.compareAndSet(specialSamplingRulesAtomic.get(), list)){
        list = specialSamplingRulesAtomic.get();
        list.add(message);
    };
 /* end of interesting code section*/

答案 7 :(得分:1)

非阻塞同步与阻塞同步相同,两者都是同步,唯一的区别是非阻塞同步总体上更快。

对于初学者,只有当多个线程访问RAM中的相同资源时,才希望使用同步。在尝试访问磁盘上的内容时,您无法使用同步,或者更好地说,您需要在磁盘上使用锁。

那说,如果没有线程阻塞,你怎么能同步?

答案是乐观的锁定。这个想法已存在至少20年。也许更多。

您可能听说过Lisp语言。事实证明,函数式语言从不修改其参数,只返回新值,因此它们永远不需要同步。

在Lisp中你可以拥有共享状态,但它变得棘手。因此,大多数程序可以并行运行,从不担心同步。

乐观锁定的想法是所有线程都愿意修改共享值,但是它们有一个局部区域来修改值,并且只在末尾应用修改,使用CAS原子地使用CAS。 Cas代表Compare And Swap,它只在一个CPU周期内执行,并且已经在CPU中实现了至少20年。

CAS的最佳解释是:https://www.cs.umd.edu/class/fall2010/cmsc433/lectures/nonBlocking.pdf

因此,如果修改中存在冲突,则只会影响其中一个编写者,其余的将完成。

此外,如果没有任何争用,非阻塞算法的执行速度比阻塞算法快得多。

Java中的非阻塞算法教程,您可以在现实生活中使用代码示例:http://tutorials.jenkov.com/java-concurrency/non-blocking-algorithms.html