Java concurrency之共享锁和ReentrantReadWriteLock_动力节点Java学院整理

ReadWriteLock 和 ReentrantReadWriteLock介绍

ReadWriteLock,顾名思义,是读写锁。它维护了一对相关的锁 — — “读取锁”和“写入锁”,一个用于读取操作,另一个用于写入操作。

“读取锁”用于只读操作,它是“共享锁”,能同时被多个线程获取。

“写入锁”用于写入操作,它是“独占锁”,写入锁只能被一个线程锁获取。

注意:不能同时存在读取锁和写入锁!

ReadWriteLock是一个接口。ReentrantReadWriteLock是它的实现类,ReentrantReadWriteLock包括子类ReadLock和WriteLock。

ReadWriteLock 和 ReentrantReadWriteLock函数列表

ReadWriteLock函数列表

// 返回用于读取操作的锁。
Lock readLock()
// 返回用于写入操作的锁。
Lock writeLock()

ReentrantReadWriteLock函数列表

// 创建一个新的 ReentrantReadWriteLock,默认是采用“非公平策略”。
ReentrantReadWriteLock()
// 创建一个新的 ReentrantReadWriteLock,fair是“公平策略”。fair为true,意味着公平策略;否则,意味着非公平策略。
ReentrantReadWriteLock(boolean fair)

// 返回当前拥有写入锁的线程,如果没有这样的线程,则返回 null。
protected Thread getOwner()
// 返回一个 collection,它包含可能正在等待获取读取锁的线程。
protected Collection<Thread> getQueuedReaderThreads()
// 返回一个 collection,它包含可能正在等待获取读取或写入锁的线程。
protected Collection<Thread> getQueuedThreads()
// 返回一个 collection,它包含可能正在等待获取写入锁的线程。
protected Collection<Thread> getQueuedWriterThreads()
// 返回等待获取读取或写入锁的线程估计数目。
int getQueueLength()
// 查询当前线程在此锁上保持的重入读取锁数量。
int getReadHoldCount()
// 查询为此锁保持的读取锁数量。
int getReadLockCount()
// 返回一个 collection,它包含可能正在等待与写入锁相关的给定条件的那些线程。
protected Collection<Thread> getWaitingThreads(Condition condition)
// 返回正等待与写入锁相关的给定条件的线程估计数目。
int getWaitQueueLength(Condition condition)
// 查询当前线程在此锁上保持的重入写入锁数量。
int getWriteHoldCount()
// 查询是否给定线程正在等待获取读取或写入锁。
boolean hasQueuedThread(Thread thread)
// 查询是否所有的线程正在等待获取读取或写入锁。
boolean hasQueuedThreads()
// 查询是否有些线程正在等待与写入锁有关的给定条件。
boolean hasWaiters(Condition condition)
// 如果此锁将公平性设置为 ture,则返回 true。
boolean isFair()
// 查询是否某个线程保持了写入锁。
boolean isWriteLocked()
// 查询当前线程是否保持了写入锁。
boolean isWriteLockedByCurrentThread()
// 返回用于读取操作的锁。
ReentrantReadWriteLock.ReadLock readLock()
// 返回用于写入操作的锁。
ReentrantReadWriteLock.WriteLock writeLock()

ReentrantReadWriteLock数据结构

ReentrantReadWriteLock的UML类图如下:

从中可以看出:

(01) ReentrantReadWriteLock实现了ReadWriteLock接口。ReadWriteLock是一个读写锁的接口,提供了"获取读锁的readLock()函数" 和 "获取写锁的writeLock()函数"。

(02) ReentrantReadWriteLock中包含:sync对象,读锁readerLock和写锁writerLock。读锁ReadLock和写锁WriteLock都实现了Lock接口。读锁ReadLock和写锁WriteLock中也都分别包含了"Sync对象",它们的Sync对象和ReentrantReadWriteLock的Sync对象 是一样的,就是通过sync,读锁和写锁实现了对同一个对象的访问。

(03) 和"ReentrantLock"一样,sync是Sync类型;而且,Sync也是一个继承于AQS的抽象类。Sync也包括"公平锁"FairSync和"非公平锁"NonfairSync。sync对象是"FairSync"和"NonfairSync"中的一个,默认是"NonfairSync"。

其中,共享锁源码相关的代码如下:

public static class ReadLock implements Lock, java.io.Serializable {
  private static final long serialVersionUID = -5992448646407690164L;
  // ReentrantReadWriteLock的AQS对象
  private final Sync sync;

  protected ReadLock(ReentrantReadWriteLock lock) {
    sync = lock.sync;
  }

  // 获取“共享锁”
  public void lock() {
    sync.acquireShared(1);
  }

  // 如果线程是中断状态,则抛出一场,否则尝试获取共享锁。
  public void lockInterruptibly() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
  }

  // 尝试获取“共享锁”
  public boolean tryLock() {
    return sync.tryReadLock();
  }

  // 在指定时间内,尝试获取“共享锁”
  public boolean tryLock(long timeout, TimeUnit unit)
      throws InterruptedException {
    return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
  }

  // 释放“共享锁”
  public void unlock() {
    sync.releaseShared(1);
  }

  // 新建条件
  public Condition newCondition() {
    throw new UnsupportedOperationException();
  }

  public String toString() {
    int r = sync.getReadLockCount();
    return super.toString() +
      "[Read locks = " + r + "]";
  }
}

说明:

ReadLock中的sync是一个Sync对象,Sync继承于AQS类,即Sync就是一个锁。ReentrantReadWriteLock中也有一个Sync对象,而且ReadLock中的sync和ReentrantReadWriteLock中的sync是对应关系。即ReentrantReadWriteLock和ReadLock共享同一个AQS对象,共享同一把锁。

ReentrantReadWriteLock中Sync的定义如下:

final Sync sync;

下面,分别从“获取共享锁”和“释放共享锁”两个方面对共享锁进行说明。

获取共享锁

获取共享锁的思想(即lock函数的步骤),是先通过tryAcquireShared()尝试获取共享锁。尝试成功的话,则直接返回;尝试失败的话,则通过doAcquireShared()不断的循环并尝试获取锁,若有需要,则阻塞等待。doAcquireShared()在循环中每次尝试获取锁时,都是通过tryAcquireShared()来进行尝试的。下面看看“获取共享锁”的详细流程。

1. lock()

lock()在ReadLock中,源码如下:

public void lock() {
  sync.acquireShared(1);
}

2. acquireShared()

Sync继承于AQS,acquireShared()定义在AQS中。源码如下:

public final void acquireShared(int arg) {
  if (tryAcquireShared(arg) < 0)
    doAcquireShared(arg);
}

说明:acquireShared()首先会通过tryAcquireShared()来尝试获取锁。

尝试成功的话,则不再做任何动作(因为已经成功获取到锁了)。

尝试失败的话,则通过doAcquireShared()来获取锁。doAcquireShared()会获取到锁了才返回。

3. tryAcquireShared()

tryAcquireShared()定义在ReentrantReadWriteLock.java的Sync中,源码如下:

protected final int tryAcquireShared(int unused) {
  Thread current = Thread.currentThread();
  // 获取“锁”的状态
  int c = getState();
  // 如果“锁”是“互斥锁”,并且获取锁的线程不是current线程;则返回-1。
  if (exclusiveCount(c) != 0 &&
    getExclusiveOwnerThread() != current)
    return -1;
  // 获取“读取锁”的共享计数
  int r = sharedCount(c);
  // 如果“不需要阻塞等待”,并且“读取锁”的共享计数小于MAX_COUNT;
  // 则通过CAS函数更新“锁的状态”,将“读取锁”的共享计数+1。
  if (!readerShouldBlock() &&
    r < MAX_COUNT &&
    compareAndSetState(c, c + SHARED_UNIT)) {
    // 第1次获取“读取锁”。
    if (r == 0) {
      firstReader = current;
      firstReaderHoldCount = 1;
    // 如果想要获取锁的线程(current)是第1个获取锁(firstReader)的线程
    } else if (firstReader == current) {
      firstReaderHoldCount++;
    } else {
      // HoldCounter是用来统计该线程获取“读取锁”的次数。
      HoldCounter rh = cachedHoldCounter;
      if (rh == null || rh.tid != current.getId())
        cachedHoldCounter = rh = readHolds.get();
      else if (rh.count == 0)
        readHolds.set(rh);
      // 将该线程获取“读取锁”的次数+1。
      rh.count++;
    }
    return 1;
  }
  return fullTryAcquireShared(current);
}

说明:tryAcquireShared()的作用是尝试获取“共享锁”。

如果在尝试获取锁时,“不需要阻塞等待”并且“读取锁的共享计数小于MAX_COUNT”,则直接通过CAS函数更新“读取锁的共享计数”,以及将“当前线程获取读取锁的次数+1”。否则,通过fullTryAcquireShared()获取读取锁。

4. fullTryAcquireShared()

fullTryAcquireShared()在ReentrantReadWriteLock中定义,源码如下:

final int fullTryAcquireShared(Thread current) {
  HoldCounter rh = null;
  for (;;) {
    // 获取“锁”的状态
    int c = getState();
    // 如果“锁”是“互斥锁”,并且获取锁的线程不是current线程;则返回-1。
    if (exclusiveCount(c) != 0) {
      if (getExclusiveOwnerThread() != current)
        return -1;
    // 如果“需要阻塞等待”。
    // (01) 当“需要阻塞等待”的线程是第1个获取锁的线程的话,则继续往下执行。
    // (02) 当“需要阻塞等待”的线程获取锁的次数=0时,则返回-1。
    } else if (readerShouldBlock()) {
      // 如果想要获取锁的线程(current)是第1个获取锁(firstReader)的线程
      if (firstReader == current) {
      } else {
        if (rh == null) {
          rh = cachedHoldCounter;
          if (rh == null || rh.tid != current.getId()) {
            rh = readHolds.get();
            if (rh.count == 0)
              readHolds.remove();
          }
        }
        // 如果当前线程获取锁的计数=0,则返回-1。
        if (rh.count == 0)
          return -1;
      }
    }
    // 如果“不需要阻塞等待”,则获取“读取锁”的共享统计数;
    // 如果共享统计数超过MAX_COUNT,则抛出异常。
    if (sharedCount(c) == MAX_COUNT)
      throw new Error("Maximum lock count exceeded");
    // 将线程获取“读取锁”的次数+1。
    if (compareAndSetState(c, c + SHARED_UNIT)) {
      // 如果是第1次获取“读取锁”,则更新firstReader和firstReaderHoldCount。
      if (sharedCount(c) == 0) {
        firstReader = current;
        firstReaderHoldCount = 1;
      // 如果想要获取锁的线程(current)是第1个获取锁(firstReader)的线程,
      // 则将firstReaderHoldCount+1。
      } else if (firstReader == current) {
        firstReaderHoldCount++;
      } else {
        if (rh == null)
          rh = cachedHoldCounter;
        if (rh == null || rh.tid != current.getId())
          rh = readHolds.get();
        else if (rh.count == 0)
          readHolds.set(rh);
        // 更新线程的获取“读取锁”的共享计数
        rh.count++;
        cachedHoldCounter = rh; // cache for release
      }
      return 1;
    }
  }
}

说明:fullTryAcquireShared()会根据“是否需要阻塞等待”,“读取锁的共享计数是否超过限制”等等进行处理。如果不需要阻塞等待,并且锁的共享计数没有超过限制,则通过CAS尝试获取锁,并返回1。

5. doAcquireShared()

doAcquireShared()定义在AQS函数中,源码如下:

private void doAcquireShared(int arg) {
  // addWaiter(Node.SHARED)的作用是,创建“当前线程”对应的节点,并将该线程添加到CLH队列中。
  final Node node = addWaiter(Node.SHARED);
  boolean failed = true;
  try {
    boolean interrupted = false;
    for (;;) {
      // 获取“node”的前一节点
      final Node p = node.predecessor();
      // 如果“当前线程”是CLH队列的表头,则尝试获取共享锁。
      if (p == head) {
        int r = tryAcquireShared(arg);
        if (r >= 0) {
          setHeadAndPropagate(node, r);
          p.next = null; // help GC
          if (interrupted)
            selfInterrupt();
          failed = false;
          return;
        }
      }
      // 如果“当前线程”不是CLH队列的表头,则通过shouldParkAfterFailedAcquire()判断是否需要等待,
      // 需要的话,则通过parkAndCheckInterrupt()进行阻塞等待。若阻塞等待过程中,线程被中断过,则设置interrupted为true。
      if (shouldParkAfterFailedAcquire(p, node) &&
        parkAndCheckInterrupt())
        interrupted = true;
    }
  } finally {
    if (failed)
      cancelAcquire(node);
  }
}

说明:doAcquireShared()的作用是获取共享锁。

它会首先创建线程对应的CLH队列的节点,然后将该节点添加到CLH队列中。CLH队列是管理获取锁的等待线程的队列。
如果“当前线程”是CLH队列的表头,则尝试获取共享锁;否则,则需要通过shouldParkAfterFailedAcquire()判断是否阻塞等待,需要的话,则通过parkAndCheckInterrupt()进行阻塞等待。

doAcquireShared()会通过for循环,不断的进行上面的操作;目的就是获取共享锁。需要注意的是:doAcquireShared()在每一次尝试获取锁时,是通过tryAcquireShared()来执行的!

释放共享锁

释放共享锁的思想,是先通过tryReleaseShared()尝试释放共享锁。尝试成功的话,则通过doReleaseShared()唤醒“其他等待获取共享锁的线程”,并返回true;否则的话,返回flase。

1. unlock()

public void unlock() {
  sync.releaseShared(1);
}

说明:该函数实际上调用releaseShared(1)释放共享锁。

2. releaseShared()

releaseShared()在AQS中实现,源码如下:

public final boolean releaseShared(int arg) {
  if (tryReleaseShared(arg)) {
    doReleaseShared();
    return true;
  }
  return false;
}

说明:releaseShared()的目的是让当前线程释放它所持有的共享锁。

它首先会通过tryReleaseShared()去尝试释放共享锁。尝试成功,则直接返回;尝试失败,则通过doReleaseShared()去释放共享锁。

3. tryReleaseShared()

tryReleaseShared()定义在ReentrantReadWriteLock中,源码如下:

protected final boolean tryReleaseShared(int unused) {
  // 获取当前线程,即释放共享锁的线程。
  Thread current = Thread.currentThread();
  // 如果想要释放锁的线程(current)是第1个获取锁(firstReader)的线程,
  // 并且“第1个获取锁的线程获取锁的次数”=1,则设置firstReader为null;
  // 否则,将“第1个获取锁的线程的获取次数”-1。
  if (firstReader == current) {
    // assert firstReaderHoldCount > 0;
    if (firstReaderHoldCount == 1)
      firstReader = null;
    else
      firstReaderHoldCount--;
  // 获取rh对象,并更新“当前线程获取锁的信息”。
  } else {

    HoldCounter rh = cachedHoldCounter;
    if (rh == null || rh.tid != current.getId())
      rh = readHolds.get();
    int count = rh.count;
    if (count <= 1) {
      readHolds.remove();
      if (count <= 0)
        throw unmatchedUnlockException();
    }
    --rh.count;
  }
  for (;;) {
    // 获取锁的状态
    int c = getState();
    // 将锁的获取次数-1。
    int nextc = c - SHARED_UNIT;
    // 通过CAS更新锁的状态。
    if (compareAndSetState(c, nextc))
      return nextc == 0;
  }
}

说明:tryReleaseShared()的作用是尝试释放共享锁。

4. doReleaseShared()

doReleaseShared()定义在AQS中,源码如下:

private void doReleaseShared() {
  for (;;) {
    // 获取CLH队列的头节点
    Node h = head;
    // 如果头节点不为null,并且头节点不等于tail节点。
    if (h != null && h != tail) {
      // 获取头节点对应的线程的状态
      int ws = h.waitStatus;
      // 如果头节点对应的线程是SIGNAL状态,则意味着“头节点的下一个节点所对应的线程”需要被unpark唤醒。
      if (ws == Node.SIGNAL) {
        // 设置“头节点对应的线程状态”为空状态。失败的话,则继续循环。
        if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
          continue;
        // 唤醒“头节点的下一个节点所对应的线程”。
        unparkSuccessor(h);
      }
      // 如果头节点对应的线程是空状态,则设置“文件点对应的线程所拥有的共享锁”为其它线程获取锁的空状态。
      else if (ws == 0 &&
           !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
        continue;        // loop on failed CAS
    }
    // 如果头节点发生变化,则继续循环。否则,退出循环。
    if (h == head)          // loop if head changed
      break;
  }
}

说明:doReleaseShared()会释放“共享锁”。它会从前往后的遍历CLH队列,依次“唤醒”然后“执行”队列中每个节点对应的线程;最终的目的是让这些线程释放它们所持有的锁。

公平共享锁和非公平共享锁

和互斥锁ReentrantLock一样,ReadLock也分为公平锁和非公平锁。

公平锁和非公平锁的区别,体现在判断是否需要阻塞的函数readerShouldBlock()是不同的。

公平锁的readerShouldBlock()的源码如下:

final boolean readerShouldBlock() {
  return hasQueuedPredecessors();
}

在公平共享锁中,如果在当前线程的前面有其他线程在等待获取共享锁,则返回true;否则,返回false。

非公平锁的readerShouldBlock()的源码如下:

final boolean readerShouldBlock() {
  return apparentlyFirstQueuedIsExclusive();
}

在非公平共享锁中,它会无视当前线程的前面是否有其他线程在等待获取共享锁。只要该非公平共享锁对应的线程不为null,则返回true。

ReentrantReadWriteLock示例

 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock; 

 public class ReadWriteLockTest1 { 

   public static void main(String[] args) {
     // 创建账户
     MyCount myCount = new MyCount("4238920615242830", 10000);
     // 创建用户,并指定账户
     User user = new User("Tommy", myCount); 

     // 分别启动3个“读取账户金钱”的线程 和 3个“设置账户金钱”的线程
     for (int i=0; i<3; i++) {
       user.getCash();
       user.setCash((i+1)*1000);
     }
   }
 } 

 class User {
   private String name;      //用户名
   private MyCount myCount;    //所要操作的账户
   private ReadWriteLock myLock;  //执行操作所需的锁对象 

   User(String name, MyCount myCount) {
     this.name = name;
     this.myCount = myCount;
     this.myLock = new ReentrantReadWriteLock();
   }

   public void getCash() {
     new Thread() {
       public void run() {
         myLock.readLock().lock();
         try {
           System.out.println(Thread.currentThread().getName() +" getCash start");
           myCount.getCash();
           Thread.sleep(1);
           System.out.println(Thread.currentThread().getName() +" getCash end");
         } catch (InterruptedException e) {
         } finally {
           myLock.readLock().unlock();
         }
       }
     }.start();
   }

   public void setCash(final int cash) {
     new Thread() {
       public void run() {
         myLock.writeLock().lock();
         try {
           System.out.println(Thread.currentThread().getName() +" setCash start");
           myCount.setCash(cash);
           Thread.sleep(1);
           System.out.println(Thread.currentThread().getName() +" setCash end");
         } catch (InterruptedException e) {
         } finally {
           myLock.writeLock().unlock();
         }
       }
     }.start();
   }
 }

 class MyCount {
   private String id;     //账号
   private int  cash;    //账户余额 

   MyCount(String id, int cash) {
     this.id = id;
     this.cash = cash;
   } 

   public String getId() {
     return id;
   } 

   public void setId(String id) {
     this.id = id;
   } 

   public int getCash() {
     System.out.println(Thread.currentThread().getName() +" getCash cash="+ cash);
     return cash;
   } 

   public void setCash(int cash) {
     System.out.println(Thread.currentThread().getName() +" setCash cash="+ cash);
     this.cash = cash;
   }
 }

运行结果:

Thread-0 getCash start
Thread-2 getCash start
Thread-0 getCash cash=10000
Thread-2 getCash cash=10000
Thread-0 getCash end
Thread-2 getCash end
Thread-1 setCash start
Thread-1 setCash cash=1000
Thread-1 setCash end
Thread-3 setCash start
Thread-3 setCash cash=2000
Thread-3 setCash end
Thread-4 getCash start
Thread-4 getCash cash=2000
Thread-4 getCash end
Thread-5 setCash start
Thread-5 setCash cash=3000
Thread-5 setCash end

结果说明:

(01) 观察Thread0和Thread-2的运行结果,我们发现,Thread-0启动并获取到“读取锁”,在它还没运行完毕的时候,Thread-2也启动了并且也成功获取到“读取锁”。

因此,“读取锁”支持被多个线程同时获取。

(02) 观察Thread-1,Thread-3,Thread-5这三个“写入锁”的线程。只要“写入锁”被某线程获取,则该线程运行完毕了,才释放该锁。

因此,“写入锁”不支持被多个线程同时获取。

(0)

相关推荐

  • ReadWriteLock接口及其实现ReentrantReadWriteLock方法

    Java并发包的locks包里的锁基本上已经介绍得差不多了,ReentrantLock重入锁是个关键,在清楚的了解了同步器AQS的运行机制后,实际上再分析这些锁就会显得容易得多,这章节主讲另外一个重要的锁--ReentrantReadWriteLock读写锁. ReentrantLock是一个独占锁,也就是说只能由一个线程获取锁,但如果场景是线程只做读的操作呢?这样ReentrantLock就不是很合适,读的线程并不需要保证其线程的安全性,任何一个线程都能去获取锁,只有这样才能尽可能地保证性能和

  • Java并发编程之显示锁ReentrantLock和ReadWriteLock读写锁

    在Java5.0之前,只有synchronized(内置锁)和volatile. Java5.0后引入了显示锁ReentrantLock. ReentrantLock概况 ReentrantLock是可重入的锁,它不同于内置锁, 它在每次使用都需要显示的加锁和解锁, 而且提供了更高级的特性:公平锁, 定时锁, 有条件锁, 可轮询锁, 可中断锁. 可以有效避免死锁的活跃性问题.ReentrantLock实现了 Lock接口: 复制代码 代码如下: public interface Lock {  

  • Java concurrency之共享锁和ReentrantReadWriteLock_动力节点Java学院整理

    ReadWriteLock 和 ReentrantReadWriteLock介绍 ReadWriteLock,顾名思义,是读写锁.它维护了一对相关的锁 - - "读取锁"和"写入锁",一个用于读取操作,另一个用于写入操作. "读取锁"用于只读操作,它是"共享锁",能同时被多个线程获取. "写入锁"用于写入操作,它是"独占锁",写入锁只能被一个线程锁获取. 注意:不能同时存在读取锁和写入锁

  • Java concurrency之互斥锁_动力节点Java学院整理

    ReentrantLock介绍 ReentrantLock是一个可重入的互斥锁,又被称为"独占锁". 顾名思义,ReentrantLock锁在同一个时间点只能被一个线程锁持有:而可重入的意思是,ReentrantLock锁,可以被单个线程多次获取. ReentrantLock分为"公平锁"和"非公平锁".它们的区别体现在获取锁的机制上是否公平."锁"是为了保护竞争资源,防止多个线程同时操作线程而出错,ReentrantLock

  • Java concurrency之Condition条件_动力节点Java学院整理

    Condition介绍 Condition的作用是对锁进行更精确的控制.Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法.不同的是,Object中的wait(),notify(),notifyAll()方法是和"同步锁"(synchronized关键字)捆绑使用的:而Condition

  • Java异常继承结构解析_动力节点Java学院整理

    Java异常类层次结构图: 异常的英文单词是exception,字面翻译就是"意外.例外"的意思,也就是非正常情况.事实上,异常本质上是程序上的错误,包括程序逻辑错误和系统错误.比如使用空的引用.数组下标越界.内存溢出错误等,这些都是意外的情况,背离我们程序本身的意图.错误在我们编写程序的过程中会经常发生,包括编译期间和运行期间的错误,在编译期间出现的错误有编译器帮助我们一起修正,然而运行期间的错误便不是编译器力所能及了,并且运行期间的错误往往是难以预料的.假若程序在运行期间出现了错误

  • Java设计模式之备忘录模式_动力节点Java学院

    定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样就可以将该对象恢复到原先保存的状态 类型:行为类 类图: 我们在编程的时候,经常需要保存对象的中间状态,当需要的时候,可以恢复到这个状态.比如,我们使用Eclipse进行编程时,假如编写失误(例如不小心误删除了几行代码),我们希望返回删除前的状态,便可以使用Ctrl+Z来进行返回.这时我们便可以使用备忘录模式来实现. 备忘录模式的结构 发起人:记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和

  • 深入理解Java中的final关键字_动力节点Java学院整理

    Java中的final关键字非常重要,它可以应用于类.方法以及变量.这篇文章中我将带你看看什么是final关键字?将变量,方法和类声明为final代表了什么?使用final的好处是什么?最后也有一些使用final关键字的实例.final经常和static一起使用来声明常量,你也会看到final是如何改善应用性能的. final关键字的含义? final在Java中是一个保留的关键字,可以声明成员变量.方法.类以及本地变量.一旦你将引用声明作final,你将不能改变这个引用了,编译器会检查代码,如

  • Java Date类常用示例_动力节点Java学院整理

    Date类 在JDK1.0中,Date类是唯一的一个代表时间的类,但是由于Date类不便于实现国际化,所以从JDK1.1版本开始,推荐使用Calendar类进行时间和日期处理.这里简单介绍一下Date类的使用. 1.使用Date类代表当前系统时间 Date d = new Date(); System.out.println(d); 使用Date类的默认构造方法创建出的对象就代表当前时间,由于Date类覆盖了toString方法,所以可以直接输出Date类型的对象,显示的结果如下: Sun Ma

  • Java设计模式之迭代器模式_动力节点Java学院整理

    定义:提供一种方法访问一个容器对象中各个元素,而又不暴露该对象的内部细节. 类型:行为类模式 类图: 如果要问Java中使用最多的一种模式,答案不是单例模式,也不是工厂模式,更不是策略模式,而是迭代器模式,先来看一段代码吧: public static void print(Collection coll){ Iterator it = coll.iterator(); while(it.hasNext()){ String str = (String)it.next(); System.out

  • Java System类详解_动力节点Java学院整理

    System类是jdk提供的一个工具类,有final修饰,不可继承,由名字可以看出来,其中的操作多数和系统相关.其功能主要如下: • 标准输入输出,如out.in.err • 外部定义的属性和环境变量的访问,如getenv()/setenv()和getProperties()/setProperties() • 加载文件和类库的方法,如load()和loadLibrary(). • 一个快速拷贝数组的方法:arraycopy() • 一些jvm操作,如gc().runFinalization()

  • Java数据结构与算法之栈(动力节点Java学院整理)

    stack,中文翻译为堆栈,其实指的是栈,heap,堆.这里讲的是数据结构的栈,不是内存分配里面的堆和栈. 栈是先进后出的数据的结构,好比你碟子一个一个堆起来,最后放的那个是堆在最上面的. 队列就是排队买苹果,先去的那个可以先买. 栈 public class Stack { private int array[]; private int max; private int top; public Stack(int max){ this.max = max; array = new int[m

随机推荐