首页 文章

同步与锁定

提问于
浏览
157

java.util.concurrent API提供了一个名为 Lock 的类,它基本上将序列化控件以访问关键资源 . 它给出了 park()unpark() 等方法 .

如果我们可以使用 synchronized 关键字并使用 wait()notify() notifyAll() 方法,我们可以做类似的事情 .

我想知道其中哪一个在实践中更好,为什么?

10 回答

  • 1

    如果你're simply locking an object, I' d喜欢使用 synchronized

    例:

    Lock.acquire();
    doSomethingNifty(); // Throws a NPE!
    Lock.release(); // Oh noes, we never release the lock!
    

    你必须在任何地方明确地做 try{} finally{} .

    鉴于同步,它是非常明确的,不可能出错:

    synchronized(myObject) {
        doSomethingNifty();
    }
    

    也就是说, Lock 对于更复杂的事物可能更有用,在这些事情中你无法以如此干净的方式获取和释放 . 老实说,我宁愿首先避免使用裸 Lock ,如果满足您的需求,只需使用更复杂的并发控制,如 CyclicBarrierLinkedBlockingQueue .

    我从来没有理由使用 wait()notify() ,但可能有一些好的 .

  • 3

    我想知道其中哪一个在实践中更好,为什么?

    我发现 LockCondition (以及其他新的 concurrent 类)只是工具箱的更多工具 . 我可以用我的旧羊角锤( synchronized 关键字)完成我需要的大部分工作,但在某些情况下使用它很尴尬 . 一旦我在工具箱中添加了更多工具,一些笨拙的情况变得更加简单:橡皮锤,圆头锤,撬棒和一些钉子 . 然而,我的旧羊角锤仍然看到它的使用份额 .

    我不认为一个人真的比另一个人好,而是每个人都更适合不同的问题 . 简而言之, synchronized 的简单模型和面向范围的特性有助于保护我免受代码中的错误的影响,但这些相同的优点有时会成为更复杂场景中的障碍 . 这些更复杂的场景是创建并发包以帮助解决的问题 . 但是使用这种更高级别的构造需要在代码中进行更明确和仔细的管理 .

    ===

    我认为JavaDoc很好地描述了 Locksynchronized 之间的区别(重点是我的):

    Lock实现提供了比使用synchronized方法和语句获得的更广泛的锁定操作 . 它们允许更灵活的结构,可能具有完全不同的属性,并且可以支持多个关联的Condition对象 . ...使用synchronized方法或语句可以访问与每个对象关联的隐式监视器锁,但强制所有锁获取和释放以块结构方式发生:当获取多个锁时,它们必须在相反的情况下释放命令,所有锁必须在获取它们的相同词法范围内释放 . 虽然同步方法和语句的作用域机制使得使用监视器锁进行编程变得更加容易,并且有助于避免许多涉及锁的常见编程错误,但有时您需要以更灵活的方式使用锁 . 例如,**用于遍历并发访问的数据结构的某些算法*需要使用“手动”或“链锁定”:获取节点A的锁,然后获取节点B,然后释放A并获取C,然后释放B并获得D等等 . Lock接口的实现允许使用这种技术,允许在不同的范围内获取和释放锁,并允许以任何顺序获取和释放多个锁 . 随着这种增加的灵活性,附加责任 . 缺少块结构化锁定会消除使用同步方法和语句发生的锁的自动释放 . 在大多数情况下,应使用以下习惯用法:...当锁定和解锁发生在不同的范围内时,必须注意确保在保持锁定时执行的所有代码都受try-finally或try-catch的保护确保在必要时释放锁定 . 锁实现通过提供获取锁(tryLock())的非阻塞尝试,尝试获取可被中断的锁(lockInterruptibly()以及尝试获取)来提供使用同步方法和语句的附加功能 . 可以超时的锁(tryLock(long,TimeUnit))....

  • 60

    您可以使用低级原语来实现java.util.concurrent中的所有实用程序 synchronizedvolatile ,或wait / notify

    但是,并发性很棘手,并且大多数人至少会对其中的某些部分进行错误处理,从而导致其代码不正确或效率低下(或两者兼而有之) .

    并发API提供了更高级别的方法,使用起来更容易(并且更安全) . 简而言之,您不应再需要直接使用 synchronized, volatile, wait, notify .

    Lock类本身位于此工具箱的较低级别,您甚至可能不需要直接使用它(您可以在大多数情况下使用 QueuesSemaphore等东西) .

  • 162

    有四个主要因素可以解释为什么要使用 synchronizedjava.util.concurrent.Lock .

    注意:当我说内部锁定时,同步锁定就是我的意思 .

    • 当Java 5推出ReentrantLocks时,他们证明了与内在锁定相比具有明显的吞吐量差异 . 如果您正在寻找更快的锁定机制并且正在运行1.5,请考虑j.u.c.ReentrantLock . Java 6的内在锁定现在可以比较 .

    • j.u.c.Lock具有不同的锁定机制 . 锁定可中断 - 尝试锁定直到锁定螺纹中断;定时锁定 - 试图锁定一段时间,如果不成功则放弃; tryLock - 尝试锁定,如果某个其他线程持有锁放弃 . 这一切都包含在简单的锁定之外 . 内在锁定仅提供简单锁定

    • 风格 . 如果1和2都不属于大多数人(包括我自己)所关注的类别,那么就会发现内部锁定的semenatics更容易阅读,而且j.u.c.Lock锁定更简洁 .

    • 多个条件 . 您锁定的对象只能被通知并等待一个案例 . Lock的newCondition方法允许单个Lock有等待或发出信号的多个原因 . 我在实践中还没有真正需要这个功能,但对于那些需要它的人来说这是一个很好的功能 .

  • 4

    我想在Bert F回答之上添加更多内容 .

    Locks 支持更细粒度的锁控制的各种方法,它们比隐式监视器更具表现力( synchronized 锁)

    Lock提供对共享资源的独占访问:一次只有一个线程可以获取锁,并且对共享资源的所有访问都需要首先获取锁 . 但是,某些锁可能允许并发访问共享资源,例如ReadWriteLock的读锁定 .

    Lock over Synchronization 的优点来自文档page

    • 使用同步方法或语句可以访问与每个对象关联的隐式监视器锁,但强制所有锁获取和释放以块结构方式发生

    • 锁实现提供了使用同步方法和语句的附加功能,方法是提供非阻塞尝试获取 lock (tryLock()) ,尝试获取可被中断的锁( lockInterruptibly() ,并尝试获取可以 timeout (tryLock(long, TimeUnit)) 的锁 .

    • Lock类还可以提供与隐式监视器锁完全不同的行为和语义,例如 guaranteed ordering, non-reentrant usage, or deadlock detection

    ReentrantLock:根据我的理解,简单来说, ReentrantLock 允许对象从一个关键部分重新进入其他关键部分 . 由于您已经锁定以输入一个关键部分,因此可以使用当前锁定在同一对象上的其他关键部分 .

    ReentrantLock 关键功能article

    • 能够无阻地锁定 .

    • 等待锁定时能够超时 .

    • 创造公平锁定的力量 .

    • API获取锁定等待线程的列表 .

    • 灵活地尝试锁定而不会阻塞 .

    您可以使用 ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock 进一步获取对读取和写入操作的粒度锁定的控制 .

    除了这三个ReentrantLocks之外,java 8还提供了一个Lock

    StampedLock:

    Java 8附带了一种名为StampedLock的新型锁,它也支持读写锁,就像上面的例子一样 . 与ReadWriteLock相比,StampedLock的锁定方法返回由long值表示的戳记 .

    您可以使用这些标记释放锁定或检查锁定是否仍然有效 . 另外,标记锁支持另一种称为乐观锁定的锁定模式 .

    看看article对不同类型的 ReentrantLockStampedLock 锁的使用情况 .

  • 14

    主要区别在于公平性,换句话说就是请求处理FIFO还是会有闯入?方法级同步确保锁的公平或FIFO分配 . 运用

    synchronized(foo) {
    }
    

    要么

    lock.acquire(); .....lock.release();
    

    不保证公平 .

    如果您对锁具有很多争用,则很容易遇到更新请求获取锁定且旧请求卡住的插入 . 我已经看到过200个线程在短时间内到达锁定而第二个到达的情况最后被处理的情况 . 对某些应用程序来说这是好的,但对于其他应用程序来说它是致命的

    有关此主题的完整讨论,请参阅Brian Goetz的“Java Concurrency In Practice”一书的第13.3节 .

  • 0

    Brian Goetz的“实践中的Java并发”一书,第13.3节:“......与默认的ReentrantLock一样,内在锁定不提供确定性公平性保证,但大多数锁定实现的统计公平性保证对于几乎所有情况都足够好......”

  • 20

    Lock使程序员的生活更轻松 . 以下几种情况可以通过锁定更轻松地实现 .

    • 锁定一个方法,并以其他方法释放锁定 .

    • 你有两个线程正在处理两个不同的代码片段,但是第一个线程依赖于那里的第二个线程来完成某段代码然后再继续进行(而其他一些线程也同时工作) . 共享锁可以很容易地解决这个问题 .

    • 实施监视器 . 例如,一个简单的队列,其中put和get方法从许多不同的线程执行 . 但是,你是否想要在一个又一个上使用相同的方法,并且put和get方法都不能重叠 . 在这种情况下,私人锁可以让生活变得更加轻松 .

    同时,锁和条件都 Build 在synchronized上 . 所以你当然可以实现同样的目标 . 但是,这可能会使您的生活变得困难,并可能使您无法解决实际问题 .

  • 5

    锁定和同步之间的主要区别是 - 使用锁定,您可以按任何顺序释放和获取锁定 . - 使用synchronized,您只能按照获取的顺序释放锁 .

  • 0

    锁定和同步块都用于相同的目的,但它取决于用途 . 考虑下面的部分

    void randomFunction(){
    .
    .
    .
    synchronize(this){
    //do some functionality
    }
    
    .
    .
    .
    synchronize(this)
    {
    // do some functionality
    }
    
    
    } // end of randomFunction
    

    在上述情况下,如果线程进入同步块,则另一个块也被锁定 . 如果同一对象上有多个这样的同步块,则所有块都被锁定 . 在这种情况下,可以使用java.util.concurrent.Lock来防止不必要的块锁定

相关问题