JDK并发包

Posted 我的技术平凡之路

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDK并发包相关的知识,希望对你有一定的参考价值。

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

1.1 ReentrantLock(重用锁)

1)与synchronized的区别是,它需要手动申请锁与解锁,在 finally 块中释放锁,而synchronized是JVM自动处理的。可控性上ReentrantLock更强。

由于ReentrantLock是重入锁,所以可以反复得到相同的一把锁,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放(重入锁)。

注:synchronized 也是可重入的,当线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个synchronized 块时,才释放锁,例如:

在带有锁的方法1里面调用了带有锁的方法2,这时在方法1获得的锁还是没有释放的,其它线程还是访问不了方法1,即使方法2结束了,直至方法1的锁释放,锁才会真正释放。假如有多个线程同时来调用方法1,那输出结果还是按顺序输出:1,2,1,2...

2)还有与synchronized不同的是,ReentrantLock对中断是有响应的。普通的lock.lock()是不能响应中断的,lock.lockInterruptibly()能够响应中断。

3)可限时,超时不能获得锁,就返回false,不会永久等待构成死锁,使用lock.tryLock(long timeout, TimeUnit unit)来实现可限时锁,参数为时间和单位。无法获得后就直接退出了。

1.2 Condition

Condition与ReentrantLock的关系就类似于synchronized与Object.wait()/signal()

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

awaitUninterruptibly()方法与await()方法基本相同,但是它并不会再等待过程中响应中断。 singal()方法用于唤醒一个在等待中的线程。相对的singalAll()方法会唤醒所有在等待中的线程。这和Obejct.notify()方法很类似。

1.3.Semaphore

对于锁来说,它是互斥的排他的。意思就是,只要我获得了锁,没人能再获得了。

而对于Semaphore来说,它允许多个线程同时进入临界区。可以认为它是一个共享锁,但是共享的额度是有限制的,额度用完了,其他没有拿到额度的线程还是要阻塞在临界区外。当额度为1时,就相等于lock。

常用方法有:

semaphore.acquire();//申请许可,当然一个线程也可以一次申请多个许可acquire(int permits)

semaphore.release();//释放许可

1.4 ReadWriteLock

ReadWriteLock是区分功能的锁。读和写是两种不同的功能,读-读不互斥,读-写互斥,写-写互斥。

这样的设计是并发量提高了,又保证了数据安全。

使用方式是:

private static ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();

1.5 CountDownLatch

倒数计时器,等待所有检查线程全部完工后,再执行。每个任务完成后调用countDown(),计数器就会减1,当计数为0的时候,await()阻塞后面的方法会继续执行。

1.6 CyclicBarrier

和CountDownLatch相似,也是等待某些线程都做完以后再执行。与CountDownLatch区别在于这个计数器可以反复使用。比如,假设我们将计数器设置为10。那么凑齐第一批1 0个线程后,计数器就会归零,然后接着凑齐下一批10个线程。并且每次完成一批线程后会触发一个动作

 CyclicBarrier(int parties, Runnable barrierAction)//barrierAction就是当计数器一次计数完成后,系统会执行的动作

也是通过await()来阻塞主线程等待任务全部完成

2. 并发容器

2.1 ConcurrentHashMap

我们知道HashMap不是一个线程安全的容器,最简单的方式使HashMap变成线程安全就是使用Collections.synchronizedMap,它是对HashMap的一个包装,如:

Collections.synchronizedMap(new HashMap())

同理对于List,Set也提供了相似方法。

但是这种方式只适合于并发量比较小的情况,它会将HashMap包装在里面,然后将HashMap的每个操作都加上synchronized。

下面来看下ConcurrentHashMap是如何实现的:

在 ConcurrentHashMap内部有一个Segment段,它将大的HashMap切分成若干个段(小的HashMap),然后让数据在每一段上Hash,这样多个线程在不同段上的Hash操作一定是线程安全的,所以只需要同步同一个段上的线程就可以了,这样实现了锁的分离,大大增加了并发量。

在使用ConcurrentHashMap.size时会比较麻烦,因为它要统计每个段的数据和,在这个时候,要把每一个段都加上锁,然后再做数据统计。这个就是把锁分离后的小小弊端,但是size方法应该是不会被高频率调用的方法。

2.2 BlockingQueue

BlockingQueue不是一个高性能的容器。但是它是一个非常好的共享数据的容器。是典型的生产者和消费者的实现。

 

以上是关于JDK并发包的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程--JDK并发包

Java高并发--------JDK并发包-------3

Java高并发--------JDK并发包-------3

JDK并发包

JDK并发包

JDK并发包[同步控制]