概念
Synchronized
Synchronized 是Java 并发编程中很重要的关键字,另外一个很重要的是 volatile。Syncronized 的目的是一次只允许一个线程进入由他修饰的代码段,从而允许他们进行自我保护。进入由 Synchronized 保护的代码区首先需要获取 Synchronized 这把锁,其他线程想要进入相同代码区执行必须等待 Synchronized 锁被释放掉,然后再争抢 Synchronized 锁,下一个抢到锁的线程才能进入相同代码区执行。
Synchronized 锁住的代码区域执行完成后会自动把锁归还,即释放锁。
Synchronized 是可重入、不可中断,非公平锁。
Lock
Lock 是 Java 并发编程中很重要的一个接口,需要手动加锁和手动解锁,一般会成对儿出现 lock.lock() 、lock.unlock()两个方法来进行加锁和解锁。 下面再介绍两个实现了 Lock 接口的锁。
ReetrantLock
可重入锁,内部定义了公平锁、非公平锁。
ReadWriteLock
内部分为读锁、写锁。将读操作和写操作进行分离,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。
这里多提一点儿 ReentrantReadWirteLock 实际上是实现了ReadWirteLock 接口,并非 Lock 接口。
使用
Synchronized
方法
在方法申明上使用 Synchronized,使进入该方法的线程同一时刻只允许进入一个,其他线程只能排队等候。
private int counter;
public synchronized void count() {
counter++;
}
代码段
在某段儿代码上使用Synchronized,使进入该段儿代码的线程同一时刻只允许进入一个,其他线程只能排队等候。
private int counter;
public synchronized void count(Object obj) {
synchronized (obj) {
counter++;
}
}
对象
对当前对象进行加锁。
private int counter;
public synchronized void count() {
synchronized (this) {
counter++;
}
}
Lock
Lock是一个接口,主要方法如下:
public interface Lock {
void lock();
void unlock();
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void lockInterruptibly() throws InterruptedException;
Condition newCondition();
}
下面将会简单地介绍一下每个方法:
- lock()
该方法是最常使用来获取锁的方法,这里需要注意的是 lock() 不会主动去释放锁,即使是发生了异常,也不会释放,所以需要通过显示的调用 unlock() 来释放锁。
Lock lock = ...;
lock.lock(); // 获取锁
try{
// do something ...
} catch (Exception ex){
// handle exception
} finally {
lock.unlock(); // 释放锁
}
- unlock()
太好理解就不再赘述了。 - trylock()
该方法是有返回值的,使用该方法来尝试获取锁,如果获取成功,则返回true;如果获取失败(即锁已被其他线程获取),则返回false。这里需要重点说的是,该方法拿不到锁不会像lock()方法一样一直等待,而是会立即返回(返回false)。 - trylock(long time, TimeUnit unit)
该方法和tryLock()方法非常类似,区别在于这个方法在拿不到锁的时候,会等待一段时间,在时间期限之内如果还拿不到锁,才会返回 false;如果一开始就拿到锁或者在等待期间内拿到了锁,则返回 true。 - lockInterruptibly()
该方法略微有些不一样,当「线程A」通过该方法去获取锁的时候,如果「线程A」需要等待(也就是说其他线程已经抢到了锁),在此场景下,「线程A」可响应中断,即中断线程的等待状态,说的再直白点儿就是「线程A」可以通过调用 interrupt() 方法来中断一直处于等待的场景。
重点问题
我们可以通过 lockInterruptibly() 与 lock()、trylock()、trylock(long time, TimeUnit unit) 几个方法的不同得出一个结论:
当一个线程获取了锁之后,该线程是不会被 interrupt() 方法中断的。interrupt() 方法只可能作用于那些处于等待状态的线程,而又不是所有处于等待的线程都可以响应 interrupt,只有那些使 lockInterruptibly() 方法获取锁但没获取到而处于等待状态的线程才会响应 interrupt。而用 synchronized 修饰的,当一个线程处于等待某个锁的状态,是无法被中断的,只能一直等待下去。
区别
- Syncronized 是Java 中的一个关键字,存在于 JVM 层面;Lock 是 Java 中的一个接口。
- 在 Syncronized 中,假设线程 A 获得锁,B 线程等待。如果 A 发生阻塞,那么 B 会一直等待;在 Lock 中,会分情况而定,Lock 中有尝试获取锁的方法(trylock),如果尝试获取到锁,返回true,无需一直等待;反之,返回false,也不会一直等待。