Java并发编程之ReentrantLock学习
程序员文章站
2022-07-15 13:55:49
...
Lock和synchronized区别
1、synchronized是java提供的内置关键字,一旦使用线程就被锁住,直到线程执行完成或者处于wait状态下才会释放;如果线程处于阻塞了就会其他线程就会一直等待,如sleep;
2、Lock是一个接口,在JDK1.5提供,属于JUC下面常用的同步处理接口。在执行同步是都需要通过方法获取锁,执行完成之后必须释放锁;在获取锁时,通过tryLock方法可以判断锁是否被占用,从而避免线程因锁被占用而阻塞。
用synchronized实现简单的加锁和解锁
private boolean isLock = false; private Thread currentThread = null; public synchronized void lock() throws InterruptedException{ while (isLock) { wait(); } isLock = true; currentThread = Thread.currentThread(); } public synchronized void unlock() throws InterruptedException{ if (this.currentThread != Thread.currentThread()) { throw new InterruptedException("当前线程没有加锁"); } else { notify(); isLock = false; currentThread = null; } } public void myLockTest() throws InterruptedException{ lock(); System.out.println(Thread.currentThread().getName() + " 获得了锁 "); try { for (int i = 0; i < 10; i++) { Thread.sleep(200); System.out.println("执行第 "+i+"次"); } } finally { System.out.println(Thread.currentThread().getName() + " 释放了锁 "); unlock(); } } public static void main(String[] args) { final MyLock lock = new MyLock(); ExecutorService service = Executors.newFixedThreadPool(2); service.submit(new Runnable() { public void run() { try { lock.myLockTest(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }); service.submit(new Runnable() { public void run() { try { lock.myLockTest(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }); service.shutdown(); }
执行结果
pool-1-thread-1 获得了锁 执行第 0次 执行第 1次 执行第 2次 pool-1-thread-1 释放了锁 pool-1-thread-2 获得了锁 执行第 0次 执行第 1次 执行第 2次 pool-1-thread-2 释放了锁
当然这只是一个实例,这种方式远没有lock接口直接和方便。
ReentrantLock
ReentrantLock是Lock的实现接口,用法比较简单;synchronized和ReentrantLock都是可重入锁,可重入锁获取是基于线程,而不是基于方法调用的分配。如下:
public void lock1(){ locks.lock(); System.out.println(Thread.currentThread().getName() + " 获得了锁 "); try { lock2(); } finally { locks.unlock(); System.out.println(Thread.currentThread().getName() + " 释放了锁 "); } } public void lock2(){ locks.lock(); System.out.println(Thread.currentThread().getName() + " 获得了锁 "); try { for (int i = 0; i < 3; i++) { System.out.println("执行第 "+i+"次"); } } finally { locks.unlock(); System.out.println(Thread.currentThread().getName() + " 释放了锁 "); } }
当调用lock1时,锁被获取,再调用lock2时,由于具备可重入性,不会出现等待或者死锁问题。
ReentrantLock 是一种独占锁,具有可重入性,独占锁是一种互斥锁,即当前线程占用锁之后,其他线程只能等待锁释放之后才能继续占有。它分为公平锁和非公平锁,公平锁是通过AQS 类中一个CLH对列(FIFO)来保证实现公平性,非公平锁是直接得到一个可获取状态的锁,不管是不是在队列头还是队列中。
ReentrantLock 关键UML图:
Lock:接口类,包括各种lock、unlock、ttyLock方法;
Sync:ReentrantLock中内部类,继承了AQS类;
NonfairSync:内部类,非公平锁实现,继承了Sync类;
FairSync:内部类,公平锁实现,继承Sync类。
NonfairSync 类解读:
lock方法实现:
final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }
直接通过CAS方法获取锁,成功则设置线程获取,失败则通过acquire方法获取,acquire是AQS类中的方法,后续会讲解。1表示的是锁的状态state。对于独占锁而言,如果所处于可获取状态,其状态为0,当锁初次被线程获取时状态变成1。
而tryAcquire方法是直接调用父类Sync中的nonfairTryAcquire方法,具体实现了非公平锁的获取
/** * 非公平锁实现 */ final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
非公平锁获取,先获取当前状态,如果为0表示处于可获取状态,通过CAS方法直接获取锁;如果当前线程就是锁的用户,则状态值增加。
FairSync类:
lock方法:
final void lock() { acquire(1); }
调用AQS类中acquire方法,与非公平锁一致,tryAcquire是重写的AQS类中的tryAcquire方法。
读写锁
ReentrantReadWriteLock就是对ReadWriteLock进行实现的,即读写互斥,准确的是说,一旦发现写锁,那么当前锁就会被独占,而如果只有读锁。这可以多个线程同时操作。
其实现和ReetrantLock差不多,使用示例:
private ReentrantReadWriteLock readLock = new ReentrantReadWriteLock(); public void read(){ readLock.readLock().lock(); if (!readLock.isWriteLocked()) { System.out.println(Thread.currentThread().getName()+"当前为读锁----"); } try { System.out.println(Thread.currentThread().getName()+"开始读操作."); for (int i=0; i<5; i++) { System.out.println(Thread.currentThread().getName()+"进行读操作..."); } System.out.println(Thread.currentThread().getName()+"读操作完毕."); } finally { System.out.println(Thread.currentThread().getName()+"释放读锁-----"); readLock.readLock().unlock(); } } public void write(){ readLock.writeLock().lock(); if (readLock.isWriteLocked()) { System.out.println(Thread.currentThread().getName()+"当前为写锁----"); } try { System.out.println(Thread.currentThread().getName()+"开始写操作."); for (int i=0; i<5; i++) { System.out.println(Thread.currentThread().getName()+"进行写操作..."); } System.out.println(Thread.currentThread().getName()+"写操作完毕."); } finally { System.out.println(Thread.currentThread().getName()+"释放写锁-----"); readLock.writeLock().unlock(); } } class ReadThread implements Runnable{ @Override public void run() { read(); } } class writeThread implements Runnable{ @Override public void run() { write(); } } public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); LockTest lockTest = new LockTest(); service.submit(lockTest.new ReadThread()); service.submit(lockTest.new ReadThread()); service.submit(lockTest.new writeThread()); service.submit(lockTest.new writeThread()); service.shutdown(); }
执行结果:
pool-1-thread-1当前为读锁---- pool-1-thread-1开始读操作. pool-1-thread-1进行读操作... pool-1-thread-2当前为读锁---- pool-1-thread-1进行读操作... pool-1-thread-2开始读操作. pool-1-thread-1进行读操作... pool-1-thread-2进行读操作... pool-1-thread-1进行读操作... pool-1-thread-2进行读操作... pool-1-thread-2进行读操作... pool-1-thread-1进行读操作... pool-1-thread-2进行读操作... pool-1-thread-1读操作完毕. pool-1-thread-2进行读操作... pool-1-thread-1释放读锁----- pool-1-thread-2读操作完毕. pool-1-thread-2释放读锁----- pool-1-thread-2当前为写锁---- pool-1-thread-2开始写操作. pool-1-thread-2进行写操作... pool-1-thread-2进行写操作... pool-1-thread-2进行写操作... pool-1-thread-2进行写操作... pool-1-thread-2进行写操作... pool-1-thread-2写操作完毕. pool-1-thread-2释放写锁----- pool-1-thread-1当前为写锁---- pool-1-thread-1开始写操作. pool-1-thread-1进行写操作... pool-1-thread-1进行写操作... pool-1-thread-1进行写操作... pool-1-thread-1进行写操作... pool-1-thread-1进行写操作... pool-1-thread-1写操作完毕. pool-1-thread-1释放写锁-----
可以看出,只有读锁时,可以多个线程共享,但是一点有写锁,那么就被独占了。
从读锁源码看到,读锁是采用的共享锁;而写锁是用的独占锁;
public static class ReadLock implements Lock, java.io.Serializable { private static final long serialVersionUID = -5992448646407690164L; private final Sync sync; /** */ protected ReadLock(ReentrantReadWriteLock lock) { sync = lock.sync; } /** * 共享锁 */ public void lock() { sync.acquireShared(1); } //略 }
写锁:
public static class WriteLock implements Lock, java.io.Serializable { private static final long serialVersionUID = -4992448646407690164L; private final Sync sync; /** */ protected WriteLock(ReentrantReadWriteLock lock) { sync = lock.sync; } /** * 独占锁.. */ public void lock() { sync.acquire(1); } //略 }