互斥锁
- JDK1.5版本提供了
java.util.concurrent.locks
包,该包中提供了锁和等待条件的接口和类,可以用于代替JDK1.5之前的synchronized
同步和监视器机制
。 - 互斥锁指的是一次最多只能有一个线程持有的锁。
- 互斥锁在Java中的体现是
Lock接口
和其实现类ReentrantLock
。 - Lock接口的出现主要替代了synchronized关键字的用处,其提供了一个比sychronized机制更广泛的锁定操作
Lock和sychronized机制的主要区别
- synchronized机制提供了对于每个对象相关的
隐式监视器锁
的访问,并强制所有锁获取和释放都要出现在一个块结构中
。 - 当
获取了多个锁
时(多个synchronized代码块嵌套
时),它们必须以相反的顺序释放
。 - synchronized机制对锁的释放是隐式的,只要线程运行的代码
超出了synchronized语句块
范围,持有的锁对象就会自动释放
。 - 锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁
- Lock机制必须是显式调用Lock对象的unlock()方法才能释放锁。
- Lock机制可以不在同一个块结构中获取和释放锁,更加自由的释放锁。
Lock接口
Lock
实现提供了使用synchronized
方法和语句所没有的其他功能,包括提供了一个非块结构的获取锁尝试tryLock()
、一个获取可中断锁的尝试lockInterruptibly()
和一个获取超时失效锁的尝试tryLock(long, TimeUnit)
。Lock
类还可以提供与隐式监视器锁完全不同的行为和语义,如保证排序、非重入用法或死锁检测。如果某个实现提供了这样特殊的语义,则该实现必须对这些语义加以记录。- 注意,
Lock
实例只是普通的对象,其本身可以在synchronized
语句中作为目标使用。获取Lock
实例的监视器锁与调用该实例的任何lock()
方法没有特别的关系。为了避免混淆,建议除了在其自身的实现中之外,决不要以这种方式使用Lock
实例。
lock
void lock()
获取锁
- 如果锁处于
空闲
状态,当前线程将直接获取该lock对象锁。 - 相反,如果
锁已经被其他线程持有,将禁用当前线程,直到当前线程获取到锁.
unlock
void unlock()
释放锁
- 当前线程将释放持有的锁。
- 锁只能由持有者释放。
- 如果线程并不持有锁,却执行了该方法,可能会导致异常的发生。
tryLock
boolean tryLock()
仅在调用时锁为空闲状态才获取该锁。
如果锁可用,则获取锁,并返回true
如果锁不可用,立即返回false
此方法可确保如果获取了锁,则会释放锁,如果未获取锁,则不会试图将其释放。即通常配合unlock()使用来释放锁。
Lock lock = new ReentrantLock(); if(lock.tryLock()){ try{ //获取到锁的一些操作 }finally{ //确保了获取锁,才能释放 lock.unlock(); } }else{ //未获取到锁的一些操作 }
tryLock()
和lock()
方法的区别:tryLock()
方法只是试图获取锁,如果锁不可用,当前线程仍然可以继续往下执行.
lock()
方法是一定要获取到锁,如果锁不可用,就会一直等待下去,锁定当前线程,在未获取指定锁对象之前,当前线程不会继续向下执行
。
Condition接口 - 条件
Condition
将Object
监视器方法wait、notify、notifyAll方法
分解成不同的对象
,为了方便通过将这些对象与任意Lock对象实现组合使用
,为每个对象提供了多个等待set(wait-set)。其中,Lock替代了synchronized方法和语句的使用,Condition替代了Object监视器方法的使用。扫描二维码关注公众号,回复: 2516844 查看本文章使用
Condition
对象的相关方法,可以方便的挂起和唤醒线程,而且可以特定的唤醒其其中某个线程
。这也是和Object对象的wait()、notify()、notifyAll()方法的区别。- Object对象的wait()、notify()、notifyAll()方法存在一个问题:如果多个线程调用了obj的wait()方法而挂起,那么我们无法做到调用obj的notify()和notifyAll()方法唤醒其中特定的一个线程,而Conditon对象就可以做到。
- Condition对象
只能通过Lock类的newCondition()方法获取
,因此一个Condition对象必然会有一个与其绑定的Lock锁
。
await
void await()
造成当前线程再接到信号或被中断之前一直处于等待状态
- 将当前线程处于
等待
状态,并释放
该Condition对象所绑定的锁
. - 使用
await()方法前
,当前线程必须持有与该Condition对象绑定的锁
,否则程序可能会抛出异常。
signal
void signal()
唤醒一个在该Condition对象上挂起的线程
- 如果存在
多个线程同时等待
该Condition对象的唤醒,则随机选择其中一个
唤醒。 - 线程
被唤醒之前,必须重新获取到锁
,即与该Condition对象绑定的Lock对象。
signalAll
void signalAll()
唤醒所有
在该Condition对象上挂起的线程
- 所有被唤醒的线程将竞争与该Condition对象绑定的锁,只有获取到锁的线程才能恢复到运行状态。
一个实例
问题的描述:
启动3个线程打印递增的数字, 线程1先打印1,2,3,4,5,
然后是线程2打印6,7,8,9,10,然后是线程3打印11,12,13,14,15.
接着再由线程1打印16,17,18,19,20....以此类推,
直到打印到75. 程序的输出结果应该为:
线程1: 1
线程1: 2
线程1: 3
线程1: 4
线程1: 5
线程2: 6
线程2: 7
线程2: 8
线程2: 9
线程2: 10
...
线程3: 71
线程3: 72
线程3: 73
线程3: 74
线程3: 75
利用ReentrantLock
和Condition
接口组合,可以轻松指定和分配各个线程该完成的操作。代码如下:
public class ReentrantLockTest {
public static void main(String[] args) {
NumPrinter n = new NumPrinter();
//此处确定每个线程应该执行几次,总共75个数分3个线程,每个线程分25个数字,
// 5*5=25,每个线程执行5次,每次打印5个数字
new Thread(()->{
//每个线程执行5次
for (int i = 0; i < 5 ; i++) {
try {
n.print1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(()->{
for (int i = 0; i <5 ; i++) {
try {
n.print2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(()->{
for (int i = 0; i < 5 ; i++) {
try {
n.print3();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
class NumPrinter{
private int num = 1;
private int flag = 1;
//使用ReentrantLock类和Condition接口来配合使用,指定唤醒哪个线程
private ReentrantLock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
public void print1() throws InterruptedException {
//三个线程同时占用临界区的资源,应该使用同步
lock.lock(); //获取锁 和 下面的 unlock 代替了 synchronized
if(flag != 1) //当flag不为1时,c1进行等待,因为此时其他的线程正在执行他们的操作
c1.await();
if(num <= 75)
for (int i = 0; i < 5; i++) {
System.out.println("1---"+num++);
}
flag = 2; //标记指定为2,在print2中限制除了flag不为2时,c2进行等待
c2.signal(); //唤醒指定的c2
lock.unlock(); //释放锁
}
public void print2() throws InterruptedException {
lock.lock();
if(flag != 2)
c2.await();
if(num <= 75)
for (int i = 0; i < 5; i++) {
System.out.println("2---"+num++);
}
flag = 3;
c3.signal();
lock.unlock();
}
public void print3() throws InterruptedException {
lock.lock();
if(flag != 3)
c3.await();
if(num <= 75)
for (int i = 0; i < 5; i++) {
System.out.println("3---"+num++);
}
flag = 1;
c1.signal();
lock.unlock();
}
}