多线程深入学习--java.util.concurrent工具类六

1.并发 Map(映射) ConcurrentMap
   介绍:(1)java.util.concurrent.ConcurrentMap 接口表示了一个能够对别人的访问(插入和提取)进行并发处理的 java.util.Map。
              (2)ConcurrentMap 除了从其父接口 java.util.Map 继承来的方法之外还有一些额外的原子性方法。
              (3)ConcurrentMap 是接口,实现类ConcurrentHashMap
              (4)ConcurrentHashMap和HashMap比较:
                        1.ConcurrentHashMap 和 java.util.HashTable 类很相似,但 ConcurrentHashMap 能够提供比HashTable 更好的并发性能。
                           说明:在你从中读取对象的时候 ConcurrentHashMap 并不会把整个Map 锁住。
                                      此外,在你向其中写入对象的时候,ConcurrentHashMap 也不会锁住整个 Map。
                                     它的内部只是把 Map 中正在被写入的部分进行锁定。
                        2.在被遍历的时候,即使是 ConcurrentHashMap 被改动,它也不会抛ConcurrentModificationException。尽管 Iterator 的设计不是为多个线程的同时使用。
     示例:
              ConcurrentMap concurrentMap = new ConcurrentHashMap();
              concurrentMap.put("key", "value");
              Object value = concurrentMap.get("key")
 
2.并发导航映射ConcurrentNavigableMap
   介绍:java.util.concurrent.ConcurrentNavigableMap 是一个支持并发访问的 java.util.NavigableMap它还能让它的子 map 具备并发访问的能力。所谓的 "子 map" 指的是诸如 headMap(),subMap(),tailMap() 之类的方法返回的 map。
   子方法:
        headMap():
              介绍:(1)headMap(T toKey) 方法返回一个包含了小于给定 toKey 的 key 的子 map。
                          (2)如果你对原始 map 里的元素做了改动,这些改动将影响到子 map 中的元素(译者注:map集合持有的其实只是对象的引用)。
           
               示例:
                      ConcurrentNavigableMap map = new ConcurrentSkipListMap();
                      map.put("1", "one");
                      map.put("2", "two");
                      map.put("3", "three");
                      ConcurrentNavigableMap headMap = map.headMap("2");
                      总结:headMap 将指向一个只含有键 "1" 的 ConcurrentNavigableMap,因为只有这一个键小于"2"。
           
        tailMap():
                介绍:(1)tailMap(T fromKey) 方法返回一个包含了不小于给定 fromKey 的 key 的子 map。
                           (2)如果你对原始 map 里的元素做了改动,这些改动将影响到子 map 中的元素(译者注:map集合持有的其实只是对象的引用)。
                示例:
                     ConcurrentNavigableMap map = new ConcurrentSkipListMap();
                     map.put("1", "one");
                     map.put("2", "two");
                     map.put("3", "three");
                     ConcurrentNavigableMap tailMap = map.tailMap("2");
                     总结:tailMap 将拥有键 "2" 和 "3",因为它们不小于给定键 "2"。
 
            subMap():
                     介绍:subMap() 方法返回原始 map 中,键介于 from(包含) 和 to (不包含) 之间的子 map。
                     示例:
                            ConcurrentNavigableMap map = new ConcurrentSkipListMap();
                            map.put("1", "one");
                            map.put("2", "two");
                            map.put("3", "three");
                            ConcurrentNavigableMap subMap = map.subMap("2", "3");
                           返回的 submap 只包含键 "2",因为只有它满足不小于 "2",比 "3" 小。
             其他方法:
                        descendingKeySet():
                              方法用来返回此映射中包含的键的顺序相反NavigableSet视图。 set的迭代器按降序返回键。该set受映射支持,所以映射的变化反映在set中,反之亦然。
                        descendingMap():
                             方法用于返回此映射中包含的映射关系的逆序视图。降序映射受此映射支持,所以映射的变化反映在降序映射中,反之亦然。
                    navigableKeySet():
                              方法用来返回此映射中包含的键的NavigableSet视图。 set的迭代器按升序返回键。该set受映射支持,所以映射的变化反映在set中,反之亦然。
                        //原文出自【易百教程】,商业转载请联系作者获得授权,非商业转载请保留原文链接:https://www.yiibai.com/java/util/treemap_navigablekeyset.html
 
                              
  3.闭锁 CountDownLatch     
     介绍:(1)java.util.concurrent.CountDownLatch 是一个并发构造,它允许一个或多个线程等待一系列指定操作的完成。
                (2)CountDownLatch 以一个给定的数量初始化。countDown() 每被调用一次,这一数量就减一。通过调用 await() 方法之一,线程可以阻塞等待这一数量到达零。
    示例:
             Decrementer 三次调用 countDown() 之后,等待中的 Waiter 才会从await() 调用中释放出来。如下:
             
            CountDownLatch latch = new CountDownLatch(3);
            Waiter waiter = new Waiter(latch);
            Decrementer decrementer = new Decrementer(latch);
            new Thread(waiter) .start();
           new Thread(decrementer).start();
           Thread.sleep(4000);
           public class Waiter implements Runnable{
                    CountDownLatch latch = null;
                   public Waiter(CountDownLatch latch) {
                             this.latch = latch;
                   }
                  public void run() {
                  try {
                       latch.await();
                   } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  System.out.println("Waiter Released");
            }
        }
       public class Decrementer implements Runnable {
                    CountDownLatch latch = null;
                    public Decrementer(CountDownLatch latch) {
                            this.latch = latch; }
                            public void run() {
                            try {
                              Thread.sleep(1000);
                              this.latch.countDown();
                              Thread.sleep(1000);
                              this.latch.countDown();
                              Thread.sleep(1000);
                              this.latch.countDown();
                     } catch (InterruptedException e) {
                                  e.printStackTrace();
                    }
               }
           }
        
 
4.栅栏 CyclicBarrier
    介绍:java.util.concurrent.CyclicBarrier 类是一种同步机制,它能够对处理一些算法的线程实现同步。
               换句话讲,它就是一个所有线程必须等待的一个栅栏,直到所有线程都到达这里,然后所有线程才可以继续做其他事情。
    图解:
              

 

       总结:通过调用 CyclicBarrier 对象的 await() 方法,两个线程可以实现互相等待。一旦 N 个线程在等待 CyclicBarrier 达成,所有线程将被释放掉去继续运行。

      创建一个 CyclicBarrier  :
              在创建一个 CyclicBarrier 的时候你需要定义有多少线程在被释放之前等待栅栏。
              创建CyclicBarrier 示例:
                        CyclicBarrier barrier = new CyclicBarrier(2);
           
       等待一个 CyclicBarrier:
              barrier.await();
             注:  当然,你也可以为等待线程设定一个超时时间。等待超过了超时时间之后,即便还没有达成N 个线程等待 CyclicBarrier 的条件,该线程也会被释放出来。以下是定义超时时间示例:barrier.await(10, TimeUnit.SECONDS);
                      满足以下任何条件都可以让等待 CyclicBarrier 的线程释放:
                           最后一个线程也到达 CyclicBarrier(调用 await())
                           当前线程被其他线程打断(其他线程调用了这个线程的 interrupt() 方法)
                           其他等待栅栏的线程被打断
                           其他等待栅栏的线程因超时而被释放
                           外部线程调用了栅栏的 CyclicBarrier.reset() 方法
         
          CyclicBarrier 行动:
                   CyclicBarrier 支持一个栅栏行动,栅栏行动是一个 Runnable 实例,一旦最后等待栅栏的线
                   程抵达,该实例将被执行。你可以在 CyclicBarrier 的构造方法中将 Runnable 栅栏行动传给它:
                   Runnable barrierAction = ... ;
                   CyclicBarrier barrier = new CyclicBarrier(2, barrierAction);
          
         CyclicBarrier 示例:
                   以下代码演示了如何使用 CyclicBarrier:
                    Runnable barrier1Action = new Runnable() {
                             public void run() {
                             System.out.println("BarrierAction 1 executed ");
                             }
                    };
                   Runnable barrier2Action = new Runnable() {
                               public void run() {
                                       System.out.println("BarrierAction 2 executed ");
                                }
                    };
                  CyclicBarrier barrier1 = new CyclicBarrier(2, barrier1Action);
                  CyclicBarrier barrier2 = new CyclicBarrier(2, barrier2Action);
                  CyclicBarrierRunnable barrierRunnable1 =new CyclicBarrierRunnable(barrier1, barrier2);
                  CyclicBarrierRunnable barrierRunnable2 =new CyclicBarrierRunnable(barrier1, barrier2);
                  new Thread(barrierRunnable1).start();
                  new Thread(barrierRunnable2).start();
                  CyclicBarrierRunnable 类:
                  public class CyclicBarrierRunnable implements Runnable{
                         CyclicBarrier barrier1 = null;
                         CyclicBarrier barrier2 = null;
                         public CyclicBarrierRunnable(CyclicBarrier barrier1,CyclicBarrier barrier2) {
                                 this.barrier1 = barrier1;
                                 this.barrier2 = barrier2;
                          }
                        public void run() {
                            try {
                               Thread.sleep(1000);
                               System.out.println(Thread.currentThread().getName() +" waiting at barrier 1");
                               this.barrier1.await();
                               Thread.sleep(1000);
                              System.out.println(Thread.currentThread().getName() +" waiting at barrier 2");
                              this.barrier2.await();
                              System.out.println(Thread.currentThread().getName() +" done!");
                           } catch (InterruptedException e) {
                              e.printStackTrace();
                           } catch (BrokenBarrierException e) {
                              e.printStackTrace();
                           }
                     }
               }
           以上代码控制台输出如下。注意每个线程写入控制台的时序可能会跟你实际执行不一样。比如有时 Thread-0 先打印,有时 Thread-1 先打印。
                        Thread-0 waiting at barrier 1
                        Thread-1 waiting at barrier 1
                        BarrierAction 1 executed
                        Thread-1 waiting at barrier 2
                        Thread-0 waiting at barrier 2
                        BarrierAction 2 executed
                        Thread-0 done!
                        Thread-1 done!
 
   5.交换机 Exchanger
              介绍:java.util.concurrent.Exchanger 类表示一种两个线程可以进行互相交换对象的会和点。
              

 

                 示例:

                          交换对象的动作由 Exchanger 的两个 exchange() 方法的其中一个完成。以下是一个示例:

                           Exchanger exchanger = new Exchanger();
                           ExchangerRunnable exchangerRunnable1 =new ExchangerRunnable(exchanger, "A");
                           ExchangerRunnable exchangerRunnable2 =new ExchangerRunnable(exchanger, "B");
                           new Thread(exchangerRunnable1).start();
                           new Thread(exchangerRunnable2).start();
                           ExchangerRunnable 代码:
                           public class ExchangerRunnable implements Runnable{
                                   Exchanger exchanger = null;
                                   Object object = null;
                                   public ExchangerRunnable(Exchanger exchanger, Object object) {
                                           this.exchanger = exchanger;
                                            this.object = object;
                                   }
                                 public void run() {
                                       try {
                                            Object previous = this.object;
                                            this.object = this.exchanger.exchange(this.object);
                                            System.out.println( Thread.currentThread().getName() +" exchanged " + previous + " for " + this.object);
                                        } catch (InterruptedException e) {
                                            e.printStackTrace();
                                        }
                                 }
                            }
                    以上程序输出:
                           Thread-0 exchanged A for B
                           Thread-1 exchanged B for A
 
 
 6.信号量 Semaphore
      介绍:java.util.concurrent.Semaphore 类是一个计数信号量。这就意味着它具备两个主要方法: acquire(),release()
                 计数信号量由一个指定数量的 "许可" 初始化。每调用一次 acquire(),一个许可会被调用线程取走。每调用一次 release(),一个许可会被返还给信号量。
                因此,在没有任何 release() 调用时,最多有 N 个线程能够通过 acquire() 方法,N 是该信号量初始化时的许可的指定数量。这些许可只是一个简单的计数器。这里没啥奇特的地方。
      用法:
               信号量主要有两种用途:
               1. 保护一个重要(代码)部分防止一次超过 N 个线程进入。
               2. 在两个线程之间发送信号。
      保护重要部分:
               如果你将信号量用于保护一个重要部分,试图进入这一部分的代码通常会首先尝试获得一个许可,然后才能进入重要部分(代码块),执行完之后,再把许可释放掉。比如这样:
              Semaphore semaphore = new Semaphore(1);
              //critical section
             semaphore.acquire();
              ...
              semaphore.release();
                  
        在线程之间发送信号:          
              如果你将一个信号量用于在两个线程之间传送信号,通常你应该用一个线程调用 acquire()方法,而另一个线程调用 release() 方法。
              如果没有可用的许可,acquire() 调用将会阻塞,直到一个许可被另一个线程释放出来。同理,如果无法往信号量释放更多许可时,一个 release() 调用也会阻塞。
              通过这个可以对多个线程进行协调。比如,如果线程 1 将一个对象插入到了一个共享列表(list)之后之后调用了 acquire(),而线程 2 则在从该列表中获取一个对象之前调用了release(),这时你其实已经创建了一个阻塞队列。
              信号量中可用的许可的数量也就等同于该阻塞队列能够持有的元素个数。
        
      公平:
              没有办法保证线程能够公平地可从信号量中获得许可。也就是说,无法担保掉第一个调用acquire() 的线程会是第一个获得一个许可的线程。
             如果第一个线程在等待一个许可时发生阻塞,而第二个线程前来索要一个许可的时候刚好有一个许可被释放出来,那么它就可能会在第一个线程之前获得许可。
             如果你想要强制公平,Semaphore 类有一个具有一个布尔类型的参数的构造子,通过这个参数以告知 Semaphore 是否要强制公平。强制公平会影响到并发性能,所以除非你确实需要它否则不要启用它。
             以下是如何在公平模式创建一个 Semaphore 的示例:
             Semaphore semaphore = new Semaphore(1, true);
        其他方法:
             java.util.concurrent.Semaphore 类还有很多方法,比如:
         availablePermits()
         acquireUninterruptibly()
         drainPermits()
         hasQueuedThreads()
         getQueuedThreads()
         tryAcquire()
 
posted @ 2020-08-17 16:11  小窝蜗  阅读(99)  评论(0编辑  收藏  举报