前面我们可以使用synchronized关键字来实现线程之间的同步互斥,lock接口同样也是在JDK1.5中提出,同样是解决线程安全性问题的另一种解决方案,而且它更强大,更灵活本片博客介绍对其展开介绍;
Lock接口有如下几个实现类:#
- ReentrantLock--JDK实现的锁
- ReentrantReadWritrLock.ReadLock
- ReentrantReadWriteLock.WriteLock
打个例子#
public class Demo01 { private int i=0; Lock Mylock = new ReentrantLock(); public int add() throws InterruptedException { try{ Mylock.lock(); i++; return i; } finally { Mylock.unlock(); } }
如上代码 i++ 被 Mylock.lock()和 Mylock.unlock()围住,显示的获取和释放锁,具有同步性...
其中,ReentrantLock一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
Lock 与 Synchronized 相比较,显而易见的就是,Lock需要显示的去获取锁,释放锁,比较繁琐,但是繁琐带来了更大好处,让代码更灵活...Lock是对Synchronized的封装...
比如:
- 在控制锁的获取和释放,以及何时何地获取释放
- 使用Lock,可以很方便的实现锁的公平性ReentrantLock(boolean fair)
- 强大的API
- 非阻塞获取锁:tryLock()
- 可中断式获取线程acquireSharedInterruptibly(int arg)
- 超时获取线程tryAcquireSharedNanos(int arg , long nanosTimeout)
一 . Conditon&Reentrantlock#
1. Condition实现正确的通知/等待#
- 注意点,一定要在condition.wait()方法调用之前,使用lock.lock()方法获取对象同步锁,否则抛出异常
public void waitMethod(){ try{ lock.lock(); System.out.println(Thread.currentThread().getName()+"等待了..."+System.currentTimeMillis()); try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } }finally { lock.unlock(); } } public void signalMethod(){ try{ lock.lock(); System.out.println(Thread.currentThread().getName()+"被唤醒了..."+System.currentTimeMillis()); condition.signal(); }finally { lock.unlock(); } } public static void main(String[] args) { demo1 demo1 = new demo1(); new Thread(()->{ demo1.waitMethod(); }).start(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } demo1.signalMethod(); } }
运行结果:
Thread-0等待了...1549450097987 main被唤醒了...1549450099988
2 使用多个Condition实现,通知部分线程#
接下来就是重头戏了Condition控制通知指定的线程醒来
public void waitMethod(){ try{ lock.lock(); System.out.println(Thread.currentThread().getName()+"等待了..."+System.currentTimeMillis()); try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } }finally { lock.unlock(); } } public void signalMethod(){ try{ lock.lock(); System.out.println(Thread.currentThread().getName()+"被唤醒了..."+System.currentTimeMillis()); condition.signal(); }finally { lock.unlock(); } } public static void main(String[] args) { demo1 demo1 = new demo1(); new Thread(()->{ demo1.waitMethod(); }).start(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } demo1.signalMethod(); } }
运行结果:
Thread-0等待了...1549450097987 main被唤醒了...1549450099988
接下来就是重头戏了Condition控制通知指定的线程醒来
/* * 使用多个Condition, 唤醒指定的线程 * */ public class demo2 { private Lock lock = new ReentrantLock(); private Condition conditionA = lock.newCondition(); private Condition conditionB = lock.newCondition(); public void waitA(){ try{ lock.lock(); System.out.println(Thread.currentThread().getName()+"等待了..."+System.currentTimeMillis()); try { conditionA.await(); System.out.println(Thread.currentThread().getName()+"await之后的代码..."+System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } }finally { lock.unlock(); } } public void waitB(){ try{ lock.lock(); System.out.println(Thread.currentThread().getName()+"等待了..."+System.currentTimeMillis()); try { conditionB.await(); System.out.println(Thread.currentThread().getName()+"await之后的代码..."+System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } }finally { lock.unlock(); } } public void signalAALL(){ try{ lock.lock(); conditionA.signalAll(); System.out.println(Thread.currentThread().getName()+" 执行唤醒操作. ."+System.currentTimeMillis()); }finally { lock.unlock(); } } public void signalBBLL(){ try{ lock.lock(); System.out.println(Thread.currentThread().getName()+" 被唤醒了.."+System.currentTimeMillis()); conditionB.signalAll(); }finally { lock.unlock(); } } public static void main(String[] args) { demo2 demo2 = new demo2(); ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(new Runnable() { @Override public void run() { demo2.waitA(); // demo2.waitB(); } }); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } demo2.signalAALL(); System.out.println("mian 线程结束..."); } }
结果:
pool-1-thread-1等待了...1549459953814 main 执行唤醒操作. .1549459955805 mian 线程结束... pool-1-thread-1await之后的代码...1549459955805
- 通过结果可以看到,ReentrantLock对象,可以唤醒指定种类的线程,使用个Condition让线程等待,就用哪个Condition去把它唤醒
公平锁与非公平锁#
Lock锁分为公平锁和非公平锁,所谓公平锁,就是表示线程获取锁的顺序,是按照线程加锁的顺序来实现的,也就是FIFO的顺序先进先出的顺序,而非公平锁描述的则是一种锁的随机抢占机制,还可能会导致一些线程根本抢不着锁而被饿死,结果就是不公平了
- ReentrantLock支持公平锁
ReentrantLock()
创建一个 ReentrantLock 的实例。
构造方法名 | 简介 |
ReentrantLock(boolean fair) | 创建一个具有给定公平策略的 ReentrantLock。 |
ReentrantLock几组常用API#
第一组:
方法名 | 作用 |
int getHoldCount() | 查询当前线程保存此锁的个数,(执行lock.lock()的次数),即重入的次数 |
int getQueueLength() | 返回**正在等待获取此锁(调用该方法的锁)的线程的个数,比如有五条线程已经准备就绪了,其中一条线程首先执行lock.await()方法,紧接着调用该方法,获取到的返回值为4,说明有四条线程正在等待此lock |
int getWaitQueueLength(Condition condition) | 返回正在等待和此锁相关的condition的线程数 |
第二组:
方法名 | 作用 |
boolean hasQueuedThread(Thread thread) | 判断当前线程是否正在等到获取到此锁 |
boolean hasQueuedThreads() | 在所有的线程中查询,是否有线程正在等待此所的锁定 |
boolean hasWaiters(Condition condition) | 判断是否有线程正在等待和此condition相关的条件 |
第三组
方法名 | 作用 |
boolean isFair() | 判断此锁是不是公平的 |
boolean isHeldByCurrentThread() | 判断当前线程是否拿到了锁 |
boolean isLocked() | 判断此锁是否由任意线程保持 |
第四组
lock()与lockInterruptibly()#
假如出现下面几步,我们这两种加锁方法,会有什么反应?,第一: 线程A启动,使用lock()加锁,第二步: CPU的执行权被线程B抢到,且线程B使用interrupted()方法给线程A打上中断的标记..第三步: 线程A继续执行
- 使用lock()加锁,假如我们没有使用isInterrupted()判断的话,代码会按照原来的顺序依次全部执行,没有异常,线程AB正常结束
- 使用lockInterruptibly()加锁,线程A被中断后,会调用lockInterruptibly()报异常,进入catch代码块
tryLock()与Lock()#
boolean tryLock()与void lock() 前者是有返回值的,两者都能去获取锁,而tryLock()直意尝试获取锁,有就拿,没有算了,它多了一步判断,它在调用时,会去判断此锁是否被其他线程锁定,如果没有,则获取,并返回ture
if(lock.tryLock()){ //do something 1 ... }else{ //do something 2... }
可以看到,使用tryLock(),不至于当前线程被阻塞住
方法名 | 作用 |
boolean tryLock(Long timeout,TimeUnit unit) | 如果在给定的等待时间内,锁没有被其他线程拿到,并且当前线程也没有被中断,获取锁 |
await()和awaitUninterrupted()#
当线程A await() 线程B给线程A打上中断的标记
- 1.中断 2,抛出中断异常 3. 进入catch块
当线程A awaitUninterrupted() 线程B给线程A打上中断的标记
- 1.中断
boolean awaitUtil(Date deadLine);#
出现一下几种情况,线程被唤醒
- 等待的时间结束
- 在等待.被中断的过程被提前唤醒
- 被中断
读写锁(排它锁,共享锁)#
在一个不需要操作实例变量的方法中,完全可以使用读写锁来提升该方法的代码运行速度
- 读操作相关的锁是共享锁,写操作相关的锁为排他锁
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
- 读读共享 lock.readLock().lock();
- 写写互斥 lock.writeLock().lock();
- 读写互斥
获取读写锁
public void read() { lock.readLock().lock(); System.out.println("获取到了读锁" + Thread.currentThread().getName() + " " + System.currentTimeMillis()); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.readLock().unlock(); } } public void write(){ try { lock.writeLock().lock(); System.out.println(""+Thread.currentThread().getName()+" " +System.currentTimeMillis()); Thread.sleep(2000); }catch (Exception e){ e.printStackTrace(); }finally { lock.writeLock().unlock(); } }
创建四条线程测试
获取到了读锁Thread-0 1548851348805 获取到了读锁Thread-1 1548851348805 Thread-2 1548851350806 Thread-3 1548851352806