多线程之三:JDK并发包

 

整理自炼数成金

源码连接:

 

1. 各种同步控制工具的使用

 

    1.1.ReentrantLock

        1.1.1. 可重入 单线程可以重复进入,但要重复退出

        1.1.2. 可中断 lockInterruptibly()

        1.1.3. 可限时 超时不能获得锁,就返回false,不会永久等待构成死锁

        1.1.4. 公平锁 先来先得 (相对不公平锁,线程调度的效率会差点)

        

[java] view plain copy

  1. public ReentrantLock(boolean fair)  
  2.   
  3. public static ReentrantLock fairLock = new ReentrantLock(true);  

 

        公平是有代价的。如果您需要公平,就必须付出代价,但是请不要把它作为您的默认选择。

 

        1.1.5. 代码示例

        

[java] view plain copy

  1. Public class Test{  
  2.             Lock lock = new ReentrantLock();    
  3.             Public void Method1(){  
  4.                 try {     
  5.                     lock.lockInterruptibly ();   
  6.                     // update object state    
  7.                 }  
  8.                 finally {  
  9.                     lock.unlock();     
  10.                 }  
  11.             }  
  12.     }  

 

        可以看到 Lock 和 synchronized 有一点明显的区别 —— lock 必须在 finally 块中释放。否则,如果受保护的代码将抛出异常,锁就有可能永远得不到释放!这一点区别看起来可能没什么,但是实际上,它极为重要。忘记在 finally 块中释放锁,可能会在程序中留下一个定时炸弹,当有一天炸弹爆炸时,您要花费很大力气才有找到源头在哪。而使用同步,JVM 将确保锁会获得自动释放。

 

    1.2.Condition

        1.2.1. 概述

        类似于 Object.wait()和Object.notify()

        与ReentrantLock结合使用

             Lock 对象则充当绑定到这个锁的条件变量的工厂对象,与标准的 wait 和 notify 方法不同,对于指定的 Lock ,可以有不止一个条件变量与它关联。这样就简化了许多并发算法的开发。例如, 条件(Condition) 的 Javadoc 显示了一个有界缓冲区实现的示例,该示例使用了两个条件变量,“not full”和“not empty”,它比每个 lock 只用一个 wait 设置的实现方式可读性要好一些(而且更有效)。

        1.2.2. 主要接口

        

[java] view plain copy

  1. void await() throws InterruptedException;  
  2.   
  3. void awaitUninterruptibly();  
  4.   
  5. long awaitNanos(long nanosTimeout) throws InterruptedException;  
  6.   
  7. boolean await(long time, TimeUnit unit) throws InterruptedException;  
  8.   
  9.  boolean awaitUntil(Date deadline) throws InterruptedException;  
  10.   
  11. void signal();  
  12.   
  13. void signalAll();  

 

        1.2.3. API详解

 

            await()方法会使当前线程等待,同时释放当前锁,当其他线程中使用signal()时或者signalAll()方法时,线 程会重新获得锁并继续执行。或者当线程被中断时,也能跳出等待。这和Object.wait()方法很相似。

            awaitUninterruptibly()方法与await()方法基本相同,但是它并不会再等待过程中响应中断。

            singal()方法用于唤醒一个在等待中的线程。相对的singalAll()方法会唤醒所有在等待中的线程。这和Obej ct.notify()方法很类似。

        1.2.4.代码示例

        

[java] view plain copy

  1. public class BlockQueue<T> {  
  2.     private ReentrantLock lock = new ReentrantLock();  
  3.     private Condition FullCondition = lock.newCondition();  
  4.     private Condition EmptyCondition = lock.newCondition();  
  5.     private List<T> list = new ArrayList<T>() ;  
  6.     private int Max;  
  7.       
  8.     public BlockQueue(int size){  
  9.         Max = size;  
  10.     }  
  11.       
  12.     public boolean push(T t){  
  13.         try{  
  14.             lock.lock();  
  15.             while(list.size()>=Max){  
  16.                 FullCondition.await();  
  17.             }  
  18.             list.add(t);  
  19.             EmptyCondition.signal();  
  20.         }catch(Exception e){  
  21.             e.printStackTrace();  
  22.         }finally{  
  23.             lock.unlock();  
  24.         }  
  25. //      synchronized(list){  
  26. //          while(true){  
  27. //              if(list.size()>=Max){  
  28. //                  try{  
  29. //                      list.wait();  
  30. //                  }catch(Exception e){  
  31. //                      e.printStackTrace();  
  32. //                  }  
  33. //              }else{  
  34. //                  break;  
  35. //              }  
  36. //          }  
  37. //          list.add(t);  
  38. //            
  39. //          list.notify();  
  40. //      }  
  41.         return true;  
  42.     }  
  43.       
  44.   
  45.       
  46.     public T poll(){  
  47.         T t = null;  
  48.         try{  
  49.             lock.lock();  
  50.             while(list.isEmpty()){  
  51.                 EmptyCondition.await();  
  52.             }  
  53.             t = list.get(0);  
  54.             list.remove(0);  
  55.             FullCondition.signal();  
  56.         }catch(Exception e){  
  57.             e.printStackTrace();  
  58.         }finally{  
  59.             lock.unlock();  
  60.         }  
  61. //      synchronized(list){  
  62. //          while(true){  
  63. //              if(list.isEmpty()){  
  64. //                  try{  
  65. //                      list.wait();  
  66. //                  }catch(Exception e){  
  67. //                      e.printStackTrace();  
  68. //                  }  
  69. //              }else{  
  70. //                  break;  
  71. //              }  
  72. //          }  
  73. //          t = list.get(0);  
  74. //          list.remove(0);  
  75. //          list.notify();  
  76. //      }  
  77.           
  78.         return t;  
  79.     }  
  80. }  

 

        该示例是自定义阻塞队列的实现,从代码可以看出,Object. notify ()会对该Object所有wait队列中随机唤醒一个,可能唤醒的不是我们想要的,例如读操作唤醒了读操作,所以可能会造成死锁。而Condition可以有多个,所以可以有目的的唤醒操作。例如只唤醒读操作或写操作。

 

    1.3. Semaphore(信号) 

        1.3.1. 概述 共享锁 运行多个线程同时临界区

        1.3.2. 主要接口

            

[java] view plain copy

  1. public void acquire()  
  2.   
  3. public void acquireUninterruptibly()  
  4.   
  5. public boolean tryAcquire()  
  6.   
  7. public boolean tryAcquire(long timeout, TimeUnit unit)  
  8.   
  9. public void release()  

 

        1.3.3.代码示例

 

            

[java] view plain copy

  1. public class TestSemaphore {  
  2.                 public static void main(String[] args) {  
  3.                 // 线程池  
  4.                 ExecutorService exec = Executors.newCachedThreadPool();  
  5.                 // 只能5个线程同时访问  
  6.                 final Semaphore semp = new Semaphore(5);  
  7.                  // 模拟20个客户端访问  
  8.                  for (int index = 0; index < 20; index++) {  
  9.                               final int NO = index;  
  10.                               Runnable run = new Runnable() {  
  11.                                                  public void run() {  
  12.                                                             try {  
  13.                                                                     // 获取许可  
  14.                                                                     semp.acquire();  
  15.                                                                     System.out.println("Accessing: " + NO);  
  16.                                                                     Thread.sleep((long) (Math.random() * 10000));  
  17.                                                                     // 访问完后,释放  
  18.                                                                     semp.release();  
  19.                                                                     System.out.println("-----------------"+semp.availablePermits());  
  20.                                                             } catch (InterruptedException e) {  
  21.                                                                     e.printStackTrace();  
  22.                                                             }  
  23.                                                   }  
  24.                                       };  
  25.                       exec.execute(run);  
  26.              }  
  27.              // 退出线程池  
  28.              exec.shutdown();  
  29.        }  
  30. }  

 

        Java 并发库 的Semaphore 可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。比如在Windows下可以设置共享文件的最大客户端访问个数。

 

    1.4.ReadWriteLock

        1.4.1. 概述

            ReadWriteLock是JDK5中提供的读写分离锁

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

        1.4.2. 访问情况

        读-读不互斥:读读之间不阻塞。

        读-写互斥:读阻塞写,写也会阻塞读。

        写-写互斥:写写阻塞。

             

        1.4.3. 主要接口

[java] view plain copy

  1. private static ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();  
  2.   
  3. private static Lock readLock = readWriteLock.readLock();  
  4.   
  5. private static Lock writeLock = readWriteLock.writeLock();  

 

        1.4.4. 代码示例 

 

[java] view plain copy

  1. class Queue3{  
  2.     private Object data = null;//共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。  
  3.     private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();  
  4.     public void get(){  
  5.         rwl.readLock().lock();//上读锁,其他线程只能读不能写  
  6.         System.out.println(Thread.currentThread().getName() + " be ready to read data!");  
  7.         try {  
  8.             Thread.sleep((long)(Math.random()*1000));  
  9.         } catch (InterruptedException e) {  
  10.             e.printStackTrace();  
  11.         }finally{  
  12.             rwl.readLock().unlock(); //释放读锁,最好放在finally里面  
  13.         }  
  14.         System.out.println(Thread.currentThread().getName() + "have read data :" + data);         
  15.           
  16.     }  
  17.     public void put(Object data){  
  18.         rwl.writeLock().lock();//上写锁,不允许其他线程读也不允许写  
  19.         System.out.println(Thread.currentThread().getName() + " be ready to write data!");                     
  20.         try {  
  21.             Thread.sleep((long)(Math.random()*1000));  
  22.             this.data = data;    
  23.         } catch (InterruptedException e) {  
  24.             e.printStackTrace();  
  25.         }finally{  
  26.             rwl.writeLock().unlock();//释放写锁   
  27.         }       
  28.         System.out.println(Thread.currentThread().getName() + " have write data: " + data);                     
  29.     }  
  30. }  

 

        代码中get方法可以多个线程同时访问,当put方法被调用时会等正在进行的get方法全部释放了读锁,然后争取到写锁。但是写锁只能1个线程同时拥有,读锁可以多个线程同时拥有。

 

 

    1.5.CountDownLatch  

        1.5.1. 概述 

        倒数计时器 

        一种典型的场景就是火箭发射。在火箭发射前,为了保证万无一失,往往还要进行各项设备、仪器的检查。 只有等所有检查完毕后,引擎才能点火。这种场景就非常适合使用CountDownLatch。它可以使得点火线程 ,等待所有检查线程全部完工后,再执行主线程 

        1.5.2. 主要接口

[java] view plain copy

  1. static final CountDownLatch end = new CountDownLatch(10);  
  2.   
  3. end.countDown();  
  4.   
  5. end.await();  

 

        1.5.3. 示意图

 

         

        1.5.4. 代码示例

[java] view plain copy

  1. public class Test {  
  2.   
  3.      public static void main(String[] args) {    
  4.   
  5.          final CountDownLatch latch = new CountDownLatch(2);  
  6.   
  7.    
  8.   
  9.          new Thread(){  
  10.   
  11.              public void run() {  
  12.   
  13.                  try {  
  14.   
  15.                      System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");  
  16.   
  17.                     Thread.sleep(3000);  
  18.   
  19.                     System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");  
  20.   
  21.                     latch.countDown();  
  22.   
  23.                 } catch (InterruptedException e) {  
  24.   
  25.                     e.printStackTrace();  
  26.   
  27.                 }  
  28.   
  29.              };  
  30.   
  31.          }.start();  
  32.   
  33.    
  34.   
  35.          new Thread(){  
  36.   
  37.              public void run() {  
  38.   
  39.                  try {  
  40.   
  41.                      System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");  
  42.   
  43.                      Thread.sleep(3000);  
  44.   
  45.                      System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");  
  46.   
  47.                      latch.countDown();  
  48.   
  49.                 } catch (InterruptedException e) {  
  50.   
  51.                     e.printStackTrace();  
  52.   
  53.                 }  
  54.   
  55.              };  
  56.   
  57.          }.start();  
  58.   
  59.    
  60.   
  61.          try {  
  62.   
  63.              System.out.println("等待2个子线程执行完毕...");  
  64.   
  65.             latch.await();  
  66.   
  67.             System.out.println("2个子线程已经执行完毕");  
  68.   
  69.             System.out.println("继续执行主线程");  
  70.   
  71.         } catch (InterruptedException e) {  
  72.   
  73.             e.printStackTrace();  
  74.   
  75.         }  
  76.   
  77.      }  
  78.   
  79. }  

 

 

 

    1.6.CyclicBarrier

        1.6.1. 概述 

        Cyclic意为循环,也就是说这个计数器可以反复使用。比如,假设我们将计数器设置为10。那么凑齐第一批1 0个线程后,计数器就会归零,然后接着凑齐下一批10个线程

        1.6.2. 主要接口

            public CyclicBarrier(int parties, Runnable barrierAction)

                barrierAction就是当计数器一次计数完成后,系统会执行的动作 await()

        1.6.3. 示意图

         

        1.6.4. 代码示例

[java] view plain copy

  1. public class Test {  
  2.     public static void main(String[] args) {  
  3.         int N = 4;  
  4.         CyclicBarrier barrier  = new CyclicBarrier(N,new Runnable() {  
  5.             @Override  
  6.             public void run() {  
  7.                 System.out.println("当前线程"+Thread.currentThread().getName());     
  8.             }  
  9.         });  
  10.    
  11.         for(int i=0;i<N;i++)  
  12.             new Writer(barrier).start();  
  13.     }  
  14.     static class Writer extends Thread{  
  15.         private CyclicBarrier cyclicBarrier;  
  16.         public Writer(CyclicBarrier cyclicBarrier) {  
  17.             this.cyclicBarrier = cyclicBarrier;  
  18.         }  
  19.    
  20.         @Override  
  21.         public void run() {  
  22.             System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据...");  
  23.             try {  
  24.                 Thread.sleep(5000);      //以睡眠来模拟写入数据操作  
  25.                 System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕");  
  26.                 cyclicBarrier.await();  
  27.             } catch (InterruptedException e) {  
  28.                 e.printStackTrace();  
  29.             }catch(BrokenBarrierException e){  
  30.                 e.printStackTrace();  
  31.             }  
  32.             System.out.println("所有线程写入完毕,继续处理其他任务...");  
  33.         }  
  34.     }  
  35. }  

 

        输出结果:

        线程Thread-0正在写入数据...

        线程Thread-3正在写入数据...

        线程Thread-2正在写入数据...

        线程Thread-1正在写入数据...

        线程Thread-2写入数据完毕,等待其他线程写入完

        线程Thread-0写入数据完毕,等待其他线程写入完

        线程Thread-3写入数据完毕,等待其他线程写入完

        线程Thread-1写入数据完毕,等待其他线程写入完

     当前线程Thread-3

        所有线程写入完毕,继续处理其他任务...

        所有线程写入完毕,继续处理其他任务...

        所有线程写入完毕,继续处理其他任务...

        所有线程写入完毕,继续处理其他任务...

 

        从上面输出结果可以看出,每个写入线程执行完写数据操作之后,就在等待其他线程写入操作完毕。

当所有线程线程写入操作完毕之后,所有线程就继续进行后续的操作了。

 

    1.7. LockSupport

        1.7.1. 概述 

        提供线程阻塞原语

        1.7.2. 主要接口 

                LockSupport.park(); 

                LockSupport.unpark(t1);

        1.7.3. 与suspend()比较 

        不容易引起线程冻结

        1.7.4. 中断响应

        能够响应中断,但不抛出异常。 中断响应的结果是,park()函数的返回,可以从Thread.interrupted()得到中断标志

    1.8.ReentrantLock 的实现

        1.8.1. CAS状态

        1.8.2. 等待队列  

        1.8.3. park()

2. 并发容器

    2.1.集合包装

        为集合类包了一层衣服,为每个方法添加了同步的实现。

    2.1.1. HashMap

        Collections.synchronizedMap

        public static Map m=Collections.synchronizedMap(new HashMap());

    2.1.2. List

        synchronizedList

    2.1.3. Set

        synchronizedSet

    2.2.ConcurrentHashMap

     高性能HashMap

     看起来是一个Map数据结构,内部实现将其划分为几个小的Map,而且每个Map都有独立的锁,形成分区锁。每个分区的读写操作不影响另一个分区。

    2.3.BlockingQueue

     阻塞队列

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值