在多线程编程中,我们如果使用到了共享资源或数据,往往是需要考虑是否线程安全的问题。这里我们先不考虑天生为多线程服务的ConcurrentMap、BlockingQueue.本文介绍下JAVA中的locks包中的锁有哪些种类,我们编程上该如何选择?下面是目录:
JAVA中的Lock
可重入锁(ReentrantLock)
读写锁(ReadWriteLock)
1. JAVA的Lock
在Java Application的编程实践中,我们会使用到多线程,这里就会牵扯到共享资源的保护。当然有些资源本身就有线程安全的保护,例如使用ConcurrentMap,BlockQueue等,天生为多线程服务的。
通常情况下,只有获得lock的线程才具有对共享资源的访问权限,但是有些锁允许并发访问共享资源,例如:ReadWriteLock.线程访问共享资源,都需要尝试获得锁才可以,java中的Lock实例只是一个普通的java对象,通过lock()就可以获得锁。
Lock具有比Synchronized更实用的方法,可以灵活设计程序。使用同步锁要求加锁和释放锁的过程要相反,同步锁比较难以应对灵活多变的需求。例如:业务逻辑A的执行需要锁定B、C,执行B的时候又需要锁定D,然后可以释放B、D,这里的锁定和释放不要求顺序,随意加锁和释放,最重要的是加锁和释放锁不需要在同一个业务块中实现。
2. 可重入锁(ReentrantLock)
最简单的解释就是:线程A的业务对共享资源加锁之后,A线程内部又一个业务又需要操作该资源,那么A线程可以直接获得锁,不需要排队等待。这就是可重入锁的解释,就是这么简单。
这就有公平和非公平之分了,有些人喜欢总结,称之为公平锁和非公平锁。公平锁是指多个线程按照申请锁的顺序来获取锁,非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,所以有可能造成线程饥饿的现象。非公平锁具有高吞吐量的特点,在业务中根据情况选择合适的使用。JDK文档中说:公平的可重入锁不参与CPU的线程调度,所以可能会有某个线程连续多次获得获得锁,尽管其他线程也是active的状态,但是CPU未分配时间片。
3. 读写锁(ReadWriteLock)
一个读写锁是一对锁:一个只读锁和一个写锁。当资源没有加写锁的时候,读锁可以同时被多个读线程共享。读写锁允许一个线程写共享数据,而多个读线程获得读锁之后并发访问这个共享数据。
通常情况下,读写锁具有更高的访问并发性,但这也需要根据读和写的频率,以及同一时刻有多少读和写而定。例如:数据读多写少的情况下使用读写锁就很合适。相反,如果写操作过多,会花费大量的时间在加锁的操作上,因此对并发并没有多大的性能提升。这里大家可以结合压力测试和性能分析,确定自己的程序是否应该使用读写锁。
在使用读写锁的时候需要考虑的事项,这也是读写锁实现需要考虑的原则:
确定读写优先级,在写少读多的情况下,读优先级比写的高,也可以使用排队公平竞争锁。
确定读锁是否需要让步于写锁,不让的话,写可能一直不能进行。让,会减少并发。
确定锁是否可重入。(ReentrantReadWriteLock)
确定是否允许锁的降级和提升,例如写锁降级成读锁,读锁提升为写锁锁。
读写锁在使用,JDK提供了ReentrantReadWriteLock类,实现了读写锁并加入了可重入的特性。
具有可重入锁的公平和非公平模式。(默认参数是非公平的,具有较高的性能)
可重入的特性。写可重入读,但读不可重入写。所有写锁释放之前,读不可重入。
锁降级。读锁可降级成写锁,在释放之前先降级成读锁,然后释放。但读锁不可提升成为写锁。
可中断。在获得锁的过程中可以中断。
条件的支持。(Condition)
可监控,具有监控手段。
更多烟台培训相关资讯,请扫描下方二维码