博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JUC 锁之java.util.concurrent.locks
阅读量:2169 次
发布时间:2019-05-01

本文共 11402 字,大约阅读时间需要 38 分钟。

此包包含三个接口,三个抽象类 AQS 六个实现类

接口

  • Condition
  • Lock
  • ReadWriteLock

抽象类

实现类

 

Condition与Lock,的方法

两个接口

condition 方法

void ()

Causes the current thread to wait until it is signalled or .

boolean (long time,  unit)

Causes the current thread to wait until it is signalled or interrupted, or the specified waiting time elapses.

long (long nanosTimeout)

Causes the current thread to wait until it is signalled or interrupted, or the specified waiting time elapses.

void ()

Causes the current thread to wait until it is signalled.

boolean ( deadline)

Causes the current thread to wait until it is signalled or interrupted, or the specified deadline elapses.

void ()

Wakes up one waiting thread.

void ()

Wakes up all waiting threads.

 

 lock方法

void ()

Acquires the lock.

void ()

Acquires the lock unless the current thread is .

()

Returns a new  instance that is bound to this Lock instance.

boolean ()

Acquires the lock only if it is free at the time of invocation.

boolean (long time,  unit)

Acquires the lock if it is free within the given waiting time and the current thread has not been .

void ()

Releases the lock.

 

    • ()

      返回用于阅读的锁。

      ()

      返回用于写入的锁。

 

以上是它们内部定义的方法。Lock中有一个newCondition方法,所以Condition都是从Lock中创建出来的。

Returns a new  instance that is bound to this Lock instance.

Before waiting on the condition the lock must be held by the current thread. A call to  will atomically release the lock before waiting and re-acquire the lock before the wait returns.

 

 Lock 接口支持那些语义不同(重入、公平等)的锁规则,可以在非阻塞式结构的上下文(包括 hand-over-hand 和锁重排算法)中使用这些规则。主要的实现是 ReentrantLock。 

        ReadWriteLock 接口以类似方式定义了一些读取者可以共享的锁, 但是把写者排除在外。此包只提供了一个实现,即 ReentrantReadWriteLock,因为它适用于大部分的标准用法上下文。但程序员可以创建自己的实现方法来覆盖非标准要求。 

        Condition 接口描述与锁有关联的条件变量。这些变量在用法上与 Object.wait 访问的隐式监视器用法类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同。

 

Condition实现原理

  以读写锁为例来看原理:

public class ReentrantLock implements Lock, java.io.Serializable {	...	public Condition newCondition() {        return sync.newCondition();    }}public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {	...	public static class ReadLock implements Lock, java.io.Serializable {		...		public Condition newCondition() {            throw new UnsupportedOperationException(); //读锁不支持Condition        }	}	...	public static class WriteLock implements Lock, java.io.Serializable {		...		public Condition newCondition() {            return sync.newCondition();        }	}}

读写锁中的读锁不支持Condition,而写锁和互斥锁支持Condition。

 

public abstract class AbstractQueuedSynchronizer    extends AbstractOwnableSynchronizer implements java.io.Serializable {    public class ConditionObject implements Condition, java.io.Serializable {    	...    	private transient Node firstWaiter;// 阻塞队列队头        private transient Node lastWaiter; // 阻塞队列队尾        ...    }}

 Sync继承自AQS,AQS有ConditionObject类,ConditionObject内定义了阻塞队列。

Sync类内定义了newCondition()方法得到ConditionObject,而写锁和互斥锁都调用了sync的newCondition()。

 

final ConditionObject newCondition() {    return new ConditionObject();}

await()实现

public final void await() throws InterruptedException {    if (Thread.interrupted())           //在执行await()时,收到中断信号,抛出异常        throw new InterruptedException();    Node node = addConditionWaiter();   //往阻塞队列加入线程    int savedState = fullyRelease(node);//在加入队列前先要先释放锁    int interruptMode = 0;    while (!isOnSyncQueue(node)) {         LockSupport.park(this);        //阻塞自己         if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)             break;    }    if (acquireQueued(node, savedState) && interruptMode != THROW_IE) //线程被唤醒后,再次获取锁        interruptMode = REINTERRUPT;        if (node.nextWaiter != null) // clean up if cancelled                unlinkCancelledWaiters();        if (interruptMode != 0)           reportInterruptAfterWait(interruptMode);   // 被中断唤醒时,向外抛出异常 }//addConditionWaiter()的具体实现,线程在调用await()时,已经先拿到锁了,//所以,在往队列添加线程时,不用CAS操作private Node addConditionWaiter() {    Node t = lastWaiter;    if (t != null && t.waitStatus != Node.CONDITION) {        unlinkCancelledWaiters();        t = lastWaiter;    }    Node node = new Node(Thread.currentThread(), Node.CONDITION);    if (t == null)        firstWaiter = node;    else        t.nextWaiter = node;    lastWaiter = node;    return node;}//checkInterruptWhileWaiting(Node node)的实现private int checkInterruptWhileWaiting(Node node) {    return Thread.interrupted() ?           (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0;}final boolean transferAfterCancelledWait(Node node) {    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) { //尝试将线程状态从条件状态设置为同步状态0,成功后将节点加入AQS同步队列尾部        enq(node);        return true;    }    while (!isOnSyncQueue(node)) // 设置失败后,自旋尝试让当前线程的CPU执行时间让出,直到唤醒的节点在AQS同步队列为止        Thread.yield();    return false;}

 

线程被唤醒有两种可能,别的线程调用unpark()或者中断唤醒。所以在被重新唤醒后要判断是否是中断唤醒,如果是,则跳出循环,然后抛出异常。而如果是被signal()唤醒的话,线程会被放到同步队列中,同样会跳出循环。

awaitUninterruptibly()的实现

 

public final void awaitUninterruptibly() {    Node node = addConditionWaiter();    int savedState = fullyRelease(node);    boolean interrupted = false;    while (!isOnSyncQueue(node)) {        LockSupport.park(this);        if (Thread.interrupted())            interrupted = true;    }    if (acquireQueued(node, savedState) || interrupted)        selfInterrupt();}

与await()的区别在于,收到中断后不会抛出异常,也就是不响应异常。

signal()的实现

public final void signal() {    if (!isHeldExclusively()) // 判断当前线程是否持有锁        throw new IllegalMonitorStateException();    Node first = firstWaiter; //获取第一个等待线程    if (first != null)        doSignal(first);}private void doSignal(Node first) { //唤醒第一个等待线程     do {         if ( (firstWaiter = first.nextWaiter) == null)             lastWaiter = null;         first.nextWaiter = null;     } while (!transferForSignal(first) &&           (first = firstWaiter) != null);}final boolean transferForSignal(Node node) {    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) //修改线程状态,从条件等待修改为运行状态0        return false;    Node p = enq(node); //将节点放进同步队列里    int ws = p.waitStatus;    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))        LockSupport.unpark(node.thread); //唤醒线程    return true;}

 Condition与Lock的关系是Condition是Lock的一部分,可以理解为一个锁Lock对应若干个条件Condition,调用await()时会将当前的线程加入到条件队列中,signal()则是将条件队列中的线程加入到同步队列中。

  调用await()的作用相当于sychronized里调用wait(),signal()则相当于notify()。

2.1 synchronized和lock对比

     java中使用锁的两个基本工具是synchronized和lock。synchronized可以用在代码快上,也可以用在方法上。synchronized是java的关键字,也是java内置的特性。那为什么从java1.5后出现了lock?

     当使用synchronized修饰一个代码块时,是通过监控当前当代码块所在类的实例对象。当一个线程获取到对应的锁,执行该代码块时,其他线程只能处于等待状态,等待当前线程释放锁,在这里释放锁的情况有两种:

       第一种:当前线程执行完毕,释放该锁

       第二种:当前线程执行异常,JVM让线程自动释放锁

     如果当前线程因为一些IO操作或者一些其他原因(比如sleep很久),那么,其他线程一直处于等待,无法获得该锁,无法进入代码块。这会大大的影响程序的效率,程序的执行。这就需要控制当前线程执行,不会让其他线程一直等待下去,Lock就能起到控制的作用。

     Lock可以实现,线程有没有获取到锁,这个synchronized是无法做到的。

     两点一定要记住:第一点 , synchronized是java内置的特性,但是Lock的功能更强大。 第二点:Lock锁的释放,是需要手动释放的,但是synchronized是自动释放的。synchronized和object对象的wait/notify/notifyall一起组合使用,效果是最好的。

   2.2 lock和condition关系

                    Condition可以替代传统的线程间通信,用await()替代wait,用signal替代notify(),用signalAll()替代notifyAll()。因为Object下面的wait/notify/notifyAll方法都是final的,所以名称上全都发生了改变。传统线程通信方式,condition都能实现。

     注意:condition()是被绑定到Lock上面的,要创建一个Lock的conditon,需要用newCondition 。现在知道了,synchronized和notidy/wait/notifyAll结合使用, lock和condition的await/signal/signalAll结合使用。

     condition的强大之处,它可以为多个线程之间创建不同的condition。

     java.util.concurrent.ArrayBlockingQueue的部分功能,实现put()和take()。假定有一个绑定的缓冲区,它支持put 和take 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存put 线程和take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个condition 实例来做到这一点。

                    代码如下:
 

class BoundedBuffer {        final Lock lock = new ReentrantLock();          //锁对象        final Condition notFull  = lock.newCondition(); //写线程锁        final Condition notEmpty = lock.newCondition(); //读线程锁              final Object[] items = new Object[100];//缓存队列        int putptr;  //写索引        int takeptr; //读索引        int count;   //队列中数据数目              //写        public void put(Object x) throws InterruptedException {          lock.lock(); //锁定          try {            // 如果队列满,则阻塞
<写线程>
while (count == items.length) { notFull.await(); } // 写入队列,并更新写索引 items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; // 唤醒
<读线程>
notEmpty.signal(); } finally { lock.unlock();//解除锁定 } } //读 public Object take() throws InterruptedException { lock.lock(); //锁定 try { // 如果队列空,则阻塞
<读线程>
while (count == 0) { notEmpty.await(); } //读取队列,并更新读索引 Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; // 唤醒
<写线程>
notFull.signal(); return x; } finally { lock.unlock();//解除锁定 } }

Lock接口的实现类和ReadWriteLock实现类

   ReentrantLock

                    ReentrantLock是Lock接口的主要实现类,当然还有另外两种实现方式。它拥有与synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)

 

class Outputter1 {            private Lock lock = new ReentrantLock();// 锁对象                  public void output(String name) {                       lock.lock();      // 得到锁                      try {                    for(int i = 0; i < name.length(); i++) {                        System.out.print(name.charAt(i));                    }                } finally {                    lock.unlock();// 释放锁                }            }        }

区别:

  需要注意的是,用sychronized修饰的方法或者语句块在代码执行完之后锁自动释放,而是用Lock需手动释放,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在try内,释放锁放在finally内!

 

ReentrantReadWriteLock

 

读取线程和写入线程互不干扰是正常的,但是 两个读线程之间不需要互斥。

     我们可以用读写锁ReadWriteLock实现:

class Data {              private int data;// 共享数据          private ReadWriteLock rwl = new ReentrantReadWriteLock();             public void set(int data) {              rwl.writeLock().lock();// 取到写锁              try {                  System.out.println(Thread.currentThread().getName() + "准备写入数据");                  try {                      Thread.sleep(20);                  } catch (InterruptedException e) {                      e.printStackTrace();                  }                  this.data = data;                  System.out.println(Thread.currentThread().getName() + "写入" + this.data);              } finally {                  rwl.writeLock().unlock();// 释放写锁              }          }              public void get() {              rwl.readLock().lock();// 取到读锁              try {                  System.out.println(Thread.currentThread().getName() + "准备读取数据");                  try {                      Thread.sleep(20);                  } catch (InterruptedException e) {                      e.printStackTrace();                  }                  System.out.println(Thread.currentThread().getName() + "读取" + this.data);              } finally {                  rwl.readLock().unlock();// 释放读锁              }          }      }

 

与互斥锁定相比,读-写锁定允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程)

     从理论上讲,与互斥锁定相比,使用读-写锁定所允许的并发性增强将带来更大的性能提高。在实践中,只有在多处理器上并且只在访问模式适用于共享数据时,才能完全实现并发性增强。——例如,某个最初用数据填充并且之后不经常对其进行修改的 collection,因为经常对其进行搜索(比如搜索某种目录),所以这样的 collection 是使用读-写锁定的理想候选者。

 

转载地址:http://uexzb.baihongyu.com/

你可能感兴趣的文章
微信小程序-调用-腾讯视频-解决方案
查看>>
phpStudy安装yaf扩展
查看>>
密码 加密 加盐 常用操作记录
查看>>
TP 分页后,调用指定页。
查看>>
Oracle数据库中的(+)连接
查看>>
java-oracle中几十个实用的PL/SQL
查看>>
PLSQL常用方法汇总
查看>>
几个基本的 Sql Plus 命令 和 例子
查看>>
PLSQL单行函数和组函数详解
查看>>
Oracle PL/SQL语言初级教程之异常处理
查看>>
Oracle PL/SQL语言初级教程之游标
查看>>
Oracle PL/SQL语言初级教程之操作和控制语言
查看>>
Oracle PL/SQL语言初级教程之过程和函数
查看>>
Oracle PL/SQL语言初级教程之表和视图
查看>>
Oracle PL/SQL语言初级教程之完整性约束
查看>>
PL/SQL学习笔记
查看>>
如何分析SQL语句
查看>>
结构化查询语言(SQL)原理
查看>>
SQL教程之嵌套SELECT语句
查看>>
几个简单的SQL例子
查看>>