zoukankan      html  css  js  c++  java
  • 多线程通信的两种方式? (可重入锁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 }
  • 相关阅读:
    [置顶] MapReduce 编程之 倒排索引
    java学习之路---线程(重点)
    CentOS下用Tomcat+Zookeeper+Nginx+Solr完美搭建SolrCloud平台(五)
    qsort的几种用法
    两道水题(月之数)(排序)
    快排
    Red and Black(简单dfs)
    zb的生日(暴搜dfs)
    又见01背包
    五子棋
  • 原文地址:https://www.cnblogs.com/zhaosq/p/11121440.html
Copyright © 2011-2022 走看看