前言
当我们了解了 多线程生成的原因 之后,会有相应的解决办法,最典型的就是 synchronized 和 lock。lock可以说是 synchronized 的一个替代品,synchronized 能做的事,lock 基本都可以做,而且能做得更好。他们的一些区别是:
- lock在获取锁的过程可以被中断。
- lock可以尝试获取锁,如果锁被其他线程持有,则返回 false,不会使当前线程休眠。
- lock在尝试获取锁的时候,传入一个时间参数,如果在这个时间范围内,没有获得锁,那么就是终止请求。
- synchronized 会自动释放锁,lock 则不会自动释放锁。
这样可以看到,lock 比起 synchronized 具有更细粒度的控制。但是也不是说 lock 就完全可以取代 synchronized,因为 lock 的学习成本,复杂度等方面要比 synchronized 高,对于初级 java 程序员,使用 synchronized 的风险要比 lock 低。
目录
包括了:
- Lock 接口方法分析
- RentrantLock
- ReadWriteLock
Java Lock接口源码分析
Lock 接口方法如下:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
lock,unlock 方法
lock() 可以用于对一段代码进行加锁,这样别的代码在锁释放之前需要进行等待,需要注意,lock不会像 synchronized 那样自动释放锁,所以:一定要放在 try-finally块中,保证锁的释放。 例如:
try {
lock.lock();
......
} finally {
lock.unlock();
}
tryLock 方法
- tryLock():尝试获得锁,如果成功,返回 true,否则,返回 false。
- tryLock(long time,TimeUnit unit):在一定的时间内尝试获得锁,并且在这段时间直接可以被打断。如果成功获得,那么将返回 true,否则,返回 false。
lockInterruptibly 方法
这里首先需要了解两个概念才能更好的理解这个方法:
- 线程的打扰机制
- Thread类的interrupt,interrupted,isInterrupted方法的区别
对于线程的打扰机制,每个线程都有一个打扰标志。
- 如果线程在sleep或wait,join,此时如果别的进程调用此进程的 interrupt()方法,此线程会被唤醒并被要求处理InterruptedException;
- 如果线程在运行,则不会收到提醒。但是 此线程的 “打扰标志”会被设置。
所以说,对于 interrupt() 方法:
- 不会中断一个正在运行的线程。
- 不会中断一个正在运行的线程。
- 不会中断一个正在运行的线程。
对于 interrupt,interrupted,isInterrupted方法的区别:
interrupt 方法上面有说到了。对于 interrupted 和 isInterrupted 方法,stackoverflow 说得很好了:
interrupted() is static and checks the current thread. isInterrupted() is an instance method which checks the Thread object that it is called on.
A common error is to call a static method on an instance.
Thread myThread = ...;
if (myThread.interrupted()) {} // WRONG! This might not be checking myThread.
if (myThread.isInterrupted()) {} // Right!
Another difference is that interrupted() also clears the status of the current thread. In other words, if you call it twice in a row and the thread is not interrupted between the two calls, the second call will return false even if the first call returned true.
The Javadocs tell you important things like this; use them often!
下面再介绍下 lockInterruptibly 方法:
当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状 态。例如当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那 么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
newCondition()
用于获取一个 Conodition 对象。Condition 对象是比 Lock 更细粒度的控制。要很好的理解 condition,个人觉得必须要知道,生产者消费者问题。
简单来说就是,我们都了解 生成者在缓冲区满了的时候需要休眠,此时会再唤起一个线程,那么你此时唤醒的是生成者还是消费者呢,如果是消费者,很好;但是如果是唤醒生产者,那还要再休眠,此时就浪费资源了。condition就可以用来解决这个问题,能保证每次唤醒的都是消费者。
lock 方法大体就介绍到这里。
ReentrantLock
可重入锁:指同一个线程,外层函数获得锁之后,内层递归函数仍有获得该锁的代码,但是不受影响。
可重入锁的最大作用就是 可以避免死锁。例如:A线程 有两个方法 a 和 b,其中 a 方法会调用 b 方法,假如 a,b 两个方法都需要获得锁,那么首先 a 方法先执行,会获得锁,此时 b方法将永远获得不了锁,b 方法将一直阻塞住, a 方法由于 b 方法没有执行完,它本身也 不释放锁,此时就会造成一个 死锁。
ReentrantLock 就是一个可重入锁。真正使用 锁的时候,一般是 Lock lock = new ReentrantLock();然后 使用 Lock 接口方法。
ReadWriteLock
接口代码如下:
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
ReadWriteLock 可以算是 Lock 的一个细分,合理使用有利于提高效率。比如说, 对于一个变量 i, A,B 线程同时读,那么不会造成错误的结果,所以此时是允许并发,但是如果是同时写操作,那么则是有可能造成错误。所以真正使用的时候,可以使用 细分需要的是读锁还是写锁,再相应地进行加锁。
Ps:从代码也可以看出,ReadWriteLock 和 Lock 没有关系,既不继承,也不是实现。