这是一个系列文章:
ReadWriteLock
读写锁可以做到在读取的时候可以被多个线程访问,但是写入的时候只能单线程访问。读读之间是共享锁,读写之间是互斥锁,写写之间也是互斥锁,ReadWriteLock就是定义了这样的一种机制,他是一个接口:
1 | public interface ReadWriteLock { |
其中读锁与写锁的条件如下:
- 读锁,它在写锁没有被任意线程获取到的时候,可以获取到锁,它允许多个线程可以同时读取共享数据,保证在读共享数据期间,没有其他线程可以对共享数据进行修改,它对读线程是共享的,对写线程是排它的
- 写锁,在读锁与写锁没有被其他线程持有的时候获得,它保证写线程以独占的方式修改共享数据,对读写的其他线程是排它的。
JDK中提供了它的实现类,即ReentrantReadWriteLock
,我们看下如何使用它。
在没有锁的情况下:
1 | public class JavaTest { |
运行上面的例子会发现,在某个线程写入的时候其他线程也会写入,同时有的线程还会读取,这就会造成并发,然后我们可以使用synchronized
或者是ReentrantLock
去实现,但是它们二者都是独占锁,即某个线程获取到了锁,其他线程就只能等待了,但是我们可以需要的是在读期间是运行线程并发的,它们做不到这点。使用ReentrantReadWriteLock
就可以做到,如下:
1 |
|
可以看到在写入的过程中是独占的,但是写读取的时候它是可以共享的,还有一点是锁降级,即可以在某种锁的代码中(lock->unlock过程中获取锁),ReentrantReadWriteLock
是支持在读锁中去调用写锁获取锁的,反之则不行。
1 | // 这是允许的锁降级 |
参考文章:读写锁 ReadWriteLock
CountDownLatch
CountDownLatch
可以做到某个操作等待其他线程执行完成之后再触发,比如A操作需要再B&C之后,就可以通过CountDownLatch实现,简单的例子:
1 | public static void main(String[] args) throws InterruptedException { |
上面例子可以保证final
是最后被打印的(其他的线程执行完成之后执行),CountDownLatch
相当于一个计数器,在构造函数中传入,通过调用它的countDown()
方法可以-1
(一般这个方法最好再finally
中去执行是最合适的),调用await()
方法就会需要等待计数器为0的时候才往下执行,还有一个await(long timeout, TimeUnit unit)
方法,假如是计数器为0或者是timeout
到了,都会往下执行。
Cyclicbarrier
它的作用是可以让所有的线程在执行前进行等待,前置操作执行完成之后才所有的线程才开始执行,也叫做栅栏。它与CountDownLatch
的区别是,CountDownLatch
等待多个线程执行完之后触发,Cyclicbarrier
是多个线程等待某个操作完成之后开始执行。
1 | public static void main(String[] args) throws InterruptedException { |
CyclicBarrier
的构造方法
CyclicBarrier(int parties)
:参数是建立几个屏障,但需要达到屏障数目之后阻塞的线程才会往下执行。CyclicBarrier(int parties, Runnable barrierAction)
,多传入一个barrierAction
,他会在屏障结束之后回调,先于阻塞线程的执行,它在那个线程执行就看那个方法先被获取到执行权。
也有另外一个await(long timeout, TimeUnit unit)
方法,当线程调用这个方法的时候,假如到达了屏障数目或者是阻塞超过timeout,线程也会开始执行。
CyclicBarrier
是独占锁的形式,它的并发性一般,它与的区别是CountDownLatch
只能使用一次,CyclicBarrier
可以调用reset
方法进行重置,CountDownLatch
是等待一组事件的完成之后被触发,CyclicBarrier
则是等待线程到达栅栏处之后被触发。
Semaphore
信号量,用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。他能保证一个资源最多同时能被N个线程访问,超出的线程需要进入等待状态。比如停车场,当没有停满的时候是允许进入的,但是当停满了就阻塞不允许继续停,假如有车驶离停车场,则会运行新的车进入。
1 | // 最多运行3个线程同时处理资源 |
需要注意的是在处理中的三个线程并不是同步的,即不是A执行完之后执行B,B执行完之后执行C,而是并发的,假如有共享数据则需要注意,Semaphore
只保证了最多能有多少个线程可以进入,无法保证进去之后的线程的并发安全。他还有其他的一些方法:
acquire(int permits)
一次需要permits
信号量许可才可以往下执行,0的时候是不阻塞,大于0的时候,需要当前有足够的未使用信号量才可以,否则会被阻塞。release(int permits)
,与上对应,也可以一次释放多少个信号量tryAcquire()
尝试获取信号量许可,不会阻塞,获取失败返回falsetryAcquire(int permits)
,尝试一次获取多个许可,返回false失败,不阻塞tryAcquire(long timeout, TimeUnit unit)
,尝试获取一个信号量许可,假如在指定时间内返回true则表示成功,false表示失败,到达时间之后(无论false/true)或者是中途获取成功则不会阻塞tryAcquire(int permits, long timeout, TimeUnit unit)
,尝试获取多个信号量许可,假如在指定时间内返回true则表示成功,false表示失败,到达时间之后(无论false/true)或者是中途获取成功则不会阻塞。
ReadWriteLock
4. AQS,ReentrantLock,Condition
5. CountDownLatch
6. CyclicBarrier
7. 线程池与Queuewa
8. 安全集合7. availablePermits()
,获取当前可用的信号量
Aomic
Java提供了基础类型的基于Unsafe
的原子操作相关类,关于它们的使用这里就不多说了。比如我们为了防止多线程重复初始化,可以使用AtomicBoolean
,比如我们需要一个自增长的Int,可以保证多线程下都不一样,可以使用AtomicInteger
集合类
多线程环境下的集合类:
- ArrayBlockingQueue,阻塞的数组队列,使用Lock实现
- LinkedBlockingDeque,阻塞的链表队列,
- SynchronousQueue,阻塞的同步队列,使用Lock实现
- ConcurrentLinkedQueue,非阻塞的队列,使用CAS实现
- ConcurrentLinkedDeque,非阻塞的双端队列,使用CAS实现
- CopyOnWriteArrayList,它是ArrayList的线程安全版本,多用于读多写少