多线程通信的两种方式? (可重入锁ReentrantLock和Object)

时间:2021-12-18 22:49:22

(一)Java中线程协作的最常见的两种方式:

(1)利用Object的wait()、notify()和notifyAll()方法及synchronized

(2)使用Condition、ReentrantLock

(二)Object类的wait()、notify()和notifyAll()方法

 /**
* Wakes up a single thread that is waiting on this object's
* monitor. If any threads are waiting on this object, one of them
* is chosen to be awakened. The choice is arbitrary and occurs at
* the discretion of the implementation. A thread waits on an object's
* monitor by calling one of the wait methods
*/
public final native void notify(); /**
* Wakes up all threads that are waiting on this object's monitor. A
* thread waits on an object's monitor by calling one of the
* wait methods.
*/
public final native void notifyAll(); /**
* Causes the current thread to wait until either another thread invokes the
* {@link java.lang.Object#notify()} method or the
* {@link java.lang.Object#notifyAll()} method for this object, or a
* specified amount of time has elapsed.
* <p>
* The current thread must own this object's monitor.
*/
public final native void wait(long timeout) throws InterruptedException;

从这三个方法的文字描述可以知道以下几点信息:

  1)wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。

  2)调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁)

  3)调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程;

  4)调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程;

(三)Lock接口,ReentrantLock、Condition说明

多线程通信的两种方式? (可重入锁ReentrantLock和Object)

3.1重要概念:可重入性

可重入性描述这样的一个问题:一个线程在持有一个锁的时候,它内部能否再次(多次)申请该锁。如果一个线程已经获得了锁,其内部还可以多次申请该锁成功。那么我们就称该锁为可重入锁。通过以下伪代码说明:

 void methodA(){
lock.lock(); // 获取锁
methodB();
lock.unlock() // 释放锁
} void methodB(){
lock.lock(); // 获取锁
// 其他业务
lock.unlock();// 释放锁
}

可重入锁可以理解为锁的一个标识。该标识具备计数器功能。标识的初始值为0,表示当前锁没有被任何线程持有。每次线程获得一个可重入锁的时候,该锁的计数器就被加1。每次一个线程释放该所的时候,该锁的计数器就减1。前提是:当前线程已经获得了该锁,是在线程的内部出现再次获取锁的场景

3.2 ReentrantLock实现说明
该demo模拟电影院的售票情况,tickets总票数。开启了10个窗口售票,售完为止

 public class ReentrantLockDemo01 implements Runnable {

     private Lock lock = new ReentrantLock();

     private int tickets = 200;

     @Override
public void run() {
while (true) {
lock.lock(); // 获取锁
try {
if (tickets > 0) {
TimeUnit.MILLISECONDS.sleep(100);
System.out.println(Thread.currentThread().getName() + " " + tickets--);
} else {
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 释放所
}
}
} public static void main(String[] args) {
ReentrantLockDemo01 reentrantLockDemo = new ReentrantLockDemo01();
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(reentrantLockDemo, "thread" + i);
thread.start();
}
}
}

3.3lockInterruptibly()方法说明

从Lock的源码可以看出:lockInterruptibly() 抛出中断异常

 void lockInterruptibly() throws InterruptedException;

3.4 tryLock(),tryLock(long time, TimeUnit unit)方法说明

tryLock()方法立刻返回当前获取情况。

tryLock(long time, TimeUnit unit)等待一定的时间,返回获取情况

 public class ReentrantLockDemo03 implements Runnable {

     private ReentrantLock lock = new ReentrantLock();

     @Override
public void run() {
try {
if (lock.tryLock(2, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + " 获取当前lock锁");
TimeUnit.SECONDS.sleep(4);
} else {
System.out.println(Thread.currentThread().getName()+ " 获取锁失败");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
} public static void main(String[] args) {
ReentrantLockDemo03 reentrantLockDemo = new ReentrantLockDemo03();
Thread thread01 = new Thread(reentrantLockDemo, "thread01");
Thread thread02 = new Thread(reentrantLockDemo, "thread02");
thread01.start();
thread02.start();
}

3.5 newCondition() 方法说明

目前只是对newCondition()使用方式进行说明,没有深入的分析Condition()的实现源码。
Condition的作用是对锁进行更精确的控制。Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法。不同的是,Object中的wait(),notify(),notifyAll()方法是和”同步锁”(synchronized关键字)捆绑使用的;而Condition是需要与”互斥锁”/”共享锁”捆绑使用的。

3.6 Condition

Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,在阻塞队列那一篇博文中就讲述到了,阻塞队列实际上是使用了Condition来模拟线程间协作。

  • Condition是个接口,基本的方法就是await()和signal()方法;
  • Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()
  • 调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用

  Conditon中的await()对应Object的wait();

  Condition中的signal()对应Object的notify();

  Condition中的signalAll()对应Object的notifyAll()。

(四)两种方式的多线程通信的实现

 package cn.csrc.base.cpu;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
*
*功能说明:线程间通信的两种方式 (1)Object (2)ReentrantLock
*@author:zsq
*create date:2019年7月2日 下午4:23:41
*修改人 修改时间 修改描述
*Copyright
*/
public class OddEvenPrinter { //第一种方法 object作为锁
private final Object obj=new Object(); //第二种方法
private final ReentrantLock lock=new ReentrantLock();
private final Condition condition=lock.newCondition(); private int limit;
private volatile int count; public OddEvenPrinter(int limit,int count){
this.limit=limit;
this.count=count;
} //Object锁
public void myPrint1(){
synchronized (obj) {
while(count<limit){
try {
System.out.println(String.format("线程[%s]打印数字:%d",Thread.currentThread().getName(),++count));
obj.notifyAll();
obj.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
}
} //ReentrantLock 重入锁
public void myPrint2(){
//一进入就加锁
lock.lock();
try{
while(count<limit){
System.out.println(String.format("线程[%s]打印数字:%d",Thread.currentThread().getName(),++count));
condition.signalAll();//唤醒被锁住的线程
}
} catch (Exception e) {
e.printStackTrace();
}finally{
//最后释放锁
lock.unlock();
}
} public static void main(String[] args) throws InterruptedException {
OddEvenPrinter print = new OddEvenPrinter(10, 0);
System.err.println("-----------第一种方法 Object-----------");
Thread thread1 = new Thread(print::myPrint1, "thread-A");
Thread thread2 = new Thread(print::myPrint1, "thread-B");
thread1.start();
thread2.start();
Thread.sleep(1000); System.err.println("-----------第二种方法 lock-----------");
Thread thread3 = new Thread(print::myPrint2, "thread-C");
Thread thread4 = new Thread(print::myPrint2, "thread-D");
thread3.start();
thread4.start();
Thread.sleep(1000);
} }