Java并发编程

CompletionService

  • ExecutorService的扩展,可以获得线程执行结果

  • 可以将生产新的异步任务与使用已完成任务的结果分离开来。生产者submit 需要执行的任务;使用者 take 已完成的任务的结果

  • 代码实例

      public class MyCompletionService implements Callable<String>{
    
      	@Override
      	public String call() throws Exception {
      		return "******callable run ********";
      	}
      			
      	public static void main(String[] args) throws Exception{
      		ExecutorService es = Executors.newCachedThreadPool();
      		CompletionService<String> completion = new ExecutorCompletionService(es);
      		completion.submit(new MyCompletionService());
      		String result = completion.take().get();
      		System.out.println(result);
      		es.shutdown();
      	}
      }
    

分支/合并(fork/join)框架

  • 按照用户指定的方式对任务进行分解,然后再将分解出的小型任务的执行结果合并成原来任务的执行结果

  • 代码实例,计算n到m之间的总和

      public class MyTask extends RecursiveTask<Integer> {
          private static final int threshold = 100;
          private int startNum;
          private int endNum;
      
          public MyTask(int startNum, int endNum) {
              this.startNum = startNum;
              this.endNum = endNum;
          }
      
          @Override
          protected Integer compute() {
              int sum = 0;
      
              if ((endNum - startNum) < 100) {
                  for (int i = startNum; i <= endNum; i++) {
                      sum += i;
                  }
              } else {
                  int middle = (startNum + endNum) / 2;
                  MyTask left = new MyTask(startNum, middle);
                  MyTask right = new MyTask(middle + 1, endNum);
      
                  left.fork();
                  right.fork();
      
                  sum = left.join() + right.join();
              }
      
              return sum;
          }
      
          public static void main(String[] args) throws Exception {
              ForkJoinPool pool = new ForkJoinPool();
              Future<Integer> result = pool.submit(new MyTask(1, 10000));
              System.out.println(result.get());
          }
      }
    

Semaphore

  • 通常用于限制访问资源的线程数目

  • 一个计数信号量。从概念上讲,信号量维护了一个许可集合。如有必要,在许可可用前会通过acquire()阻塞每一个线程,直到可以获取该许可,线程每次调用release()均释放一个许可,从而可能会让另外一个线程获得许可。事实上Semaphore不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。

  • Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。

  • 这里是一个实际的情况,大家排队上厕所,厕所只有两个位置,来了10个人需要排队

      public class MySemaphore extends Thread {
          private Semaphore position;
          private int id;
      
          public MySemaphore(int i, Semaphore s) {
              this.id = i;
              this.position = s;
          }
      
          public void run() {
              try {
                  if (position.availablePermits() > 0) {
                      System.out.println("顾客[" + this.id + "]进入厕所,有空位");//这里只是查询到有空位,但是线程不一定能够得到空位,毕竟还没有真正的获取
                  } else {
                      System.out.println("顾客[" + this.id + "]进入厕所,没空位,排队");//这里只是查询到没有空位,但是线程也可能得到空位,因为在此期间可能有线程已经释放了空位
                  }
      
                  position.acquire();
                  System.out.println("顾客[" + this.id + "]获得坑位");
                  Thread.sleep((int) (Math.random() * 1000));
                  System.out.println("顾客[" + this.id + "]使用完毕");
                  position.release();
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      
          public static void main(String[] args) {
              ExecutorService list = Executors.newCachedThreadPool();
              Semaphore position = new Semaphore(2);
      
              for (int i = 0; i < 10; i++) {
                  list.submit(new MySemaphore(i + 1, position));
              }
      
              list.shutdown();
              position.acquireUninterruptibly(2);
              System.out.println("使用完毕,需要清扫了");
              position.release(2);
          }
      }
    

ReentrantLock

  • ReentrantLock与synchronized的比较

    • 相同:ReentrantLock提供了synchronized类似的功能和内存语义
    • 不同:
      • ReentrantLock功能性方面更全面,比如时间锁等候,可中断锁等候,锁投票等,因此更有扩展性。在多个条件变量和高度竞争锁的地方,用ReentrantLock更合适,ReentrantLock还提供了Condition,对线程的等待和唤醒等操作更加灵活,一个ReentrantLock可以有多个Condition实例,所以更有扩展性。
      • ReentrantLock 的性能比synchronized会好点。
      • ReentrantLock提供了可轮询的锁请求,他可以尝试的去取得锁,如果取得成功则继续处理,取得不成功,可以等下次运行的时候处理,所以不容易产生死锁,而synchronized则一旦进入锁请求要么成功,要么一直阻塞,所以更容易产生死锁
  • 实例代码:

      Lock lock = new ReentrantLock();  
      lock.lock();  
      try {   
      	// update object state  
      }finally {  
      	lock.unlock();   
      } 
    

ReentrantReadWriteLock

  • 读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁

  • 锁机制的特性:

    • 重入方面其内部的WriteLock可以获取ReadLock,但是反过来ReadLock想要获得WriteLock则永远都不要想。
    • WriteLock可以降级为ReadLock,顺序是:先获得WriteLock再获得ReadLock,然后释放WriteLock,这时候线程将保持Readlock的持有。反过来ReadLock想要升级为WriteLock则不可能,为什么?参看(a),呵呵.
    • ReadLock可以被多个线程持有并且在作用时排斥任何的WriteLock,而WriteLock则是完全的互斥。这一特性最为重要,因为对于高读取频率而相对较低写入的数据结构,使用此类锁同步机制则可以提高并发量。
    • 不管是ReadLock还是WriteLock都支持Interrupt,语义与ReentrantLock一致。
    • WriteLock支持Condition并且与ReentrantLock语义一致,而ReadLock则不能使用Condition,否则抛出UnsupportedOperationException异常
  • 实例代码

      public class ReadWriteLockTest {
          public static void main(String[] args) {
              final Queue3 q3 = new Queue3();
      
              for (int i = 0; i < 3; i++) {
                  new Thread() {
                          public void run() {
                              while (true) {
                                  q3.get();
                              }
                          }
                      }.start();
              }
      
              for (int i = 0; i < 3; i++) {
                  new Thread() {
                          public void run() {
                              while (true) {
                                  q3.put(new Random().nextInt(10000));
                              }
                          }
                      }.start();
              }
          }
      }
    
    
      class Queue3 {
          private Object data = null; //共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。
          private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
      
          public void get() {
              rwl.readLock().lock(); //上读锁,其他线程只能读不能写
              System.out.println(Thread.currentThread().getName() +
                  " be ready to read data!");
      
              try {
                  Thread.sleep((long) (Math.random() * 1000));
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
      
              System.out.println(Thread.currentThread().getName() +
                  "have read data :" + data);
              rwl.readLock().unlock(); //释放读锁,最好放在finnaly里面
          }
      
          public void put(Object data) {
              rwl.writeLock().lock(); //上写锁,不允许其他线程读也不允许写
              System.out.println(Thread.currentThread().getName() +
                  " be ready to write data!");
      
              try {
                  Thread.sleep((long) (Math.random() * 1000));
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
      
              this.data = data;
              System.out.println(Thread.currentThread().getName() +
                  " have write data: " + data);
      
              rwl.writeLock().unlock(); //释放写锁    
          }
      }
    

BlockingQueue

  • 是一个线程安全的队列,可以解决生产者/消费者的问题,在调用put方法向队列中插入元素时,如果队列已满,它会让插入元素的线程等待队列腾出空间;在调用take方法从队列中取元素时,如果队列为空,取出元素的线程就会阻塞,可以用TransferQueue代替BlockingQueue,因为它可以获得更好的性能

  • 代码示例

      BlockingQueue<String> buffer = new LinkedBlockingQueue<String>();
      buffer.put("hehe");
      String str = buffer.take();
      //上面代码在多线程环境不会报数组越界异常
    

CountDownLatch

  • 一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待

  • CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务

  • 主要方法

    • public CountDownLatch(int count);
    • public void countDown();//当前线程调用此方法,则计数减一
    • public void await();//调用此方法会一直阻塞当前线程,直到计时器的值为0
  • 代码示例

      public class CountDownLatchDemo {
          final static SimpleDateFormat sdf = new SimpleDateFormat(
                  "yyyy-MM-dd HH:mm:ss");
      
          public static void main(String[] args) throws InterruptedException {
              CountDownLatch latch = new CountDownLatch(2); //两个工人的协作  
              Worker worker1 = new Worker("zhang san", 5000, latch);
              Worker worker2 = new Worker("li si", 8000, latch);
              worker1.start(); //  
              worker2.start(); //  
              latch.await(); //等待所有工人完成工作  
              System.out.println("all work done at " + sdf.format(new Date()));
          }
      
          static class Worker extends Thread {
              String workerName;
              int workTime;
              CountDownLatch latch;
      
              public Worker(String workerName, int workTime, CountDownLatch latch) {
                  this.workerName = workerName;
                  this.workTime = workTime;
                  this.latch = latch;
              }
      
              public void run() {
                  System.out.println("Worker " + workerName + " do work begin at " +
                      sdf.format(new Date()));
                  doWork(); //工作了  
                  System.out.println("Worker " + workerName +
                      " do work complete at " + sdf.format(new Date()));
                  latch.countDown(); //工人完成工作,计数器减一  
              }
      
              private void doWork() {
                  try {
                      Thread.sleep(workTime);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
          }
      }
    

CyclicBarrier

  • 让线程执行到await()方法就暂停下来,当所有线程都执行了await()方法,所有线程立刻一齐 执行下面的代码

  • 代码实例(赛跑时,等待所有人都准备好时,才起跑)

      public class CyclicBarrierTest {
          public static void main(String[] args)
              throws IOException, InterruptedException {
              //如果将参数改为4,但是下面只加入了3个选手,这永远等待下去  
              //Waits until all parties have invoked await on this barrier.   
              CyclicBarrier barrier = new CyclicBarrier(3);
      
              ExecutorService executor = Executors.newFixedThreadPool(3);
              executor.submit(new Thread(new Runner(barrier, "1号选手")));
              executor.submit(new Thread(new Runner(barrier, "2号选手")));
              executor.submit(new Thread(new Runner(barrier, "3号选手")));
      
              executor.shutdown();
          }
      }
      
      
      class Runner implements Runnable {
          // 一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)  
          private CyclicBarrier barrier;
          private String name;
      
          public Runner(CyclicBarrier barrier, String name) {
              super();
              this.barrier = barrier;
              this.name = name;
          }
      
          @Override
          public void run() {
              try {
                  Thread.sleep(1000 * (new Random()).nextInt(8));
                  System.out.println(name + " 准备好了...");
                  // barrier的await方法,在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。  
                  barrier.await();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              } catch (BrokenBarrierException e) {
                  e.printStackTrace();
              }
      
              System.out.println(name + " 起跑!");
          }
      }
    
  • CountDownLatch和CyclicBarrier的区别

    • 闭锁CountDownLatch做减计数,而栅栏CyclicBarrier则是加计数
    • CountDownLatch是一次性的,CyclicBarrier可以重用
    • CountDownLatch强调一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行CyclicBarrier强调N个线程相互等待,任何一个线程没完成之前,所有的线程都必须等待
    • 鉴于上面的描述,CyclicBarrier在一些场景中可以替代CountDownLatch实现类似的功能

ConcurrentHashMap

  • 线程安全,效率比HashTable更高

  • 原理: 它引入了一个“分段锁”的概念,具体可以理解为把一个大的Map拆分成N个小的HashTable,根据key.hashCode()来决定把key放到哪个HashTable中。在ConcurrentHashMap中, 就是把Map分成了N个Segment,put和get的时候,都是现根据key.hashCode()算出放到哪个Segment中ConcurrentHashMap中默认是把segments初始化为长度为16的数组

      ConcurrentHashMap<Integer, Integer> map = new ConcurrentHashMap<Integer, Integer>();
      			map.put(3, 33);
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值