在整个并发编程中,或者说管程中,考虑到了两个问题,分别是互斥和同步问题
互斥,即为同一时刻只能有一个线程访问资源
同步,即为线程之间的通信问题
这就用过Lock和Contition来实现管程,并且Lock解决互斥问题,Condition解决同步问题
为什么会在拥有了Synchronized之后,还会出现Lock这个方法吗
其主要原因是因为,Synchronized在申请资源的时候,如果申请不到,就直接进入了阻塞状态,这种状态中,啥都干不了,连释放已经拥有的资源都没办法解决,进入了线程阻塞中
所以为了解决这个问题,Lock被提出了
新增了三个强而有力的功能
1.能够响应中断,在线程中断的时候,可以释放曾经持有的锁
2.支持超时,如果一段时间没有获取到锁,会进行释放锁(返回一个错误)
3.非阻塞的获取到锁,如果尝试获取到锁失败,并不进入阻塞状态,直接返回
关于Lock如何保证的可见性的
如果是synchronized 的话,保证可见是因为Happens-Before
对于Lock,则是利用了Volatile相关的Happens-Before
在Lock中,有一个volatile的变量state,获取锁的时候,会读写State的值
也利用了这个State的值的改变会对之后操作可见
其具体的流程:
1.对于线程T1,对state的操作Before于unlock();
2.对于线程T2,unlock Before lock
3.那么T1对state的操作对T2可见
顺便说下Lock的具体实现 ReentrantLock,翻译为可重入锁
也就是说,在同一线程中,可以重复的获取到通一把锁
同样,还有可重入函数,可重入函数,是指多个线程同时调用该函数,每个线程都能得到正确的结果,同时支持在一个线程内的线程切换,这就说明可重入函数是线程安全的
在可重入锁中,还有着公平锁和非公平锁
使用ReentranLock时候,可以在构造函数中指定,是否是个公平锁
公平锁的概念很简单,如果是公平锁,那么线程在唤醒等待队列中的线程中的时候,就按照谁等待的时间长就唤醒谁
非公平锁则不提供这个保证,可能导致线程的饥饿
对于使用锁,我们规定了三个原则
永远只在更新对象的成员变量的时候加锁
永远在访问可变的成员变量的时候加锁
永远不再调用其他对象的方法的时候加锁
最后一个,是为了避免在调用其他类的方法的时候加锁,如果其他类中也加锁了,导致了死锁问题的出现
然后是关于Condition,基于Lock的条件
Condition实现了管程中的条件变量
Condition支持了多个条件变量,这是一个显著的进步
例如,实现一个阻塞队列,就要求两个条件方法
在一个Lock中
新建两个Condition
分别对应不同的情况来进行await和signal
Lock和Condition实现的管程,通知和等待需要使用的是await() signal() signAll() 这三个方法
其等价于wait(),notify(),notifyAll()
Condition中不能使用wait()这三个方法,使用了必定出现错误