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


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

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

           (2)使用Condition、ReentrantLock 

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

     1 /**
     2  * Wakes up a single thread that is waiting on this object's
     3  * monitor. If any threads are waiting on this object, one of them
     4  * is chosen to be awakened. The choice is arbitrary and occurs at
     5  * the discretion of the implementation. A thread waits on an object's
     6  * monitor by calling one of the wait methods
     7  */
     8 public final native void notify();
     9  
    10 /**
    11  * Wakes up all threads that are waiting on this object's monitor. A
    12  * thread waits on an object's monitor by calling one of the
    13  * wait methods.
    14  */
    15 public final native void notifyAll();
    16  
    17 /**
    18  * Causes the current thread to wait until either another thread invokes the
    19  * {@link java.lang.Object#notify()} method or the
    20  * {@link java.lang.Object#notifyAll()} method for this object, or a
    21  * specified amount of time has elapsed.
    22  * <p>
    23  * The current thread must own this object's monitor.
    24  */
    25 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说明

    3.1重要概念:可重入性

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

     1 void methodA(){
     2     lock.lock(); // 获取锁
     3     methodB();
     4     lock.unlock() // 释放锁
     5 }
     6 
     7 void methodB(){
     8     lock.lock(); // 获取锁
     9     // 其他业务
    10     lock.unlock();// 释放锁
    11 }

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

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

     1 public class ReentrantLockDemo01 implements Runnable {
     2 
     3     private Lock lock = new ReentrantLock();
     4 
     5     private int tickets = 200;
     6 
     7     @Override
     8     public void run() {
     9         while (true) {
    10             lock.lock(); // 获取锁
    11             try {
    12                 if (tickets > 0) {
    13                     TimeUnit.MILLISECONDS.sleep(100);
    14                     System.out.println(Thread.currentThread().getName() + " " + tickets--);
    15                 } else {
    16                     break;
    17                 }
    18             } catch (InterruptedException e) {
    19                 e.printStackTrace();
    20             } finally {
    21                 lock.unlock(); // 释放所
    22             }
    23         }
    24     }
    25 
    26     public static void main(String[] args) {
    27         ReentrantLockDemo01 reentrantLockDemo = new ReentrantLockDemo01();
    28         for (int i = 0; i < 10; i++) {
    29             Thread thread = new Thread(reentrantLockDemo, "thread" + i);
    30             thread.start();
    31         }
    32     }
    33 }

    3.3lockInterruptibly()方法说明

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

    1 void lockInterruptibly() throws InterruptedException;

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

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

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

     1 public class ReentrantLockDemo03 implements Runnable {
     2 
     3     private ReentrantLock lock = new ReentrantLock();
     4 
     5     @Override
     6     public void run() {
     7         try {
     8             if (lock.tryLock(2, TimeUnit.SECONDS)) {
     9                 System.out.println(Thread.currentThread().getName() + " 获取当前lock锁");
    10                 TimeUnit.SECONDS.sleep(4);
    11             } else {
    12                 System.out.println(Thread.currentThread().getName()+ " 获取锁失败");
    13             }
    14         } catch (InterruptedException e) {
    15             e.printStackTrace();
    16         } finally {
    17             if (lock.isHeldByCurrentThread()) {
    18                 lock.unlock();
    19             }
    20         }
    21     }
    22 
    23 
    24     public static void main(String[] args) {
    25         ReentrantLockDemo03 reentrantLockDemo = new ReentrantLockDemo03();
    26         Thread thread01 = new Thread(reentrantLockDemo, "thread01");
    27         Thread thread02 = new Thread(reentrantLockDemo, "thread02");
    28         thread01.start();
    29         thread02.start();
    30     }

    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()。

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

     1 package cn.csrc.base.cpu;
     2 import java.util.concurrent.locks.Condition;
     3 import java.util.concurrent.locks.ReentrantLock;
     4 /**
     5  * 
     6  *功能说明:线程间通信的两种方式 (1)Object (2)ReentrantLock
     7  *@author:zsq
     8  *create date:2019年7月2日 下午4:23:41
     9  *修改人   修改时间  修改描述
    10  *Copyright 
    11  */
    12 public class OddEvenPrinter {
    13 
    14     //第一种方法 object作为锁
    15     private  final Object obj=new Object(); 
    16     
    17     //第二种方法
    18     private final ReentrantLock lock=new ReentrantLock();
    19     private final Condition condition=lock.newCondition();
    20     
    21     private  int limit;
    22     private volatile int count;
    23     
    24     public OddEvenPrinter(int limit,int count){
    25         this.limit=limit;
    26         this.count=count;
    27     }
    28     
    29     //Object锁
    30     public void myPrint1(){
    31         synchronized (obj) {
    32             while(count<limit){
    33                 try {
    34                     System.out.println(String.format("线程[%s]打印数字:%d",Thread.currentThread().getName(),++count));
    35                     obj.notifyAll();
    36                     obj.wait();
    37                 } catch (Exception e) {
    38                     e.printStackTrace();
    39                 }
    40             }
    41         }
    42     }
    43     
    44     //ReentrantLock 重入锁
    45     public void myPrint2(){
    46         //一进入就加锁
    47         lock.lock();
    48         try{
    49             while(count<limit){
    50                 System.out.println(String.format("线程[%s]打印数字:%d",Thread.currentThread().getName(),++count));
    51                 condition.signalAll();//唤醒被锁住的线程
    52             }
    53         } catch (Exception e) {
    54             e.printStackTrace();
    55         }finally{
    56             //最后释放锁
    57             lock.unlock();
    58         }
    59     }
    60     
    61     public static void main(String[] args) throws InterruptedException {
    62          OddEvenPrinter print = new OddEvenPrinter(10, 0);
    63          System.err.println("-----------第一种方法 Object-----------");
    64          Thread thread1 = new Thread(print::myPrint1, "thread-A");
    65          Thread thread2 = new Thread(print::myPrint1, "thread-B");
    66          thread1.start();
    67          thread2.start();
    68          Thread.sleep(1000);
    69          
    70          System.err.println("-----------第二种方法 lock-----------");
    71          Thread thread3 = new Thread(print::myPrint2, "thread-C");
    72          Thread thread4 = new Thread(print::myPrint2, "thread-D");
    73          thread3.start();
    74          thread4.start();
    75          Thread.sleep(1000);
    76     }
    77 
    78 }
  • 相关阅读:
    autocomplete自动完成搜索提示仿google提示效果
    实现子元素相对于父元素左右居中
    javascript 事件知识集锦
    让 IE9 以下的浏览器支持 Media Queries
    「2013124」Cadence ic5141 installation on CentOS 5.5 x86_64 (limited to personal use)
    「2013420」SciPy, Numerical Python, matplotlib, Enthought Canopy Express
    「2013324」ClipSync, Youdao Note, GNote
    「2013124」XDMCP Configuration for Remote Access to Linux Desktop
    「2013115」Pomodoro, Convert Multiple CD ISO to One DVD ISO HowTo.
    「2013123」CentOS 5.5 x86_64 Installation and Configuration (for Univ. Labs)
  • 原文地址:https://www.cnblogs.com/zhaosq/p/11121440.html
Copyright © 2020-2023  润新知