可中断
可以设置超时时间
可以设置为公平锁
支持多个条件变量,即对与不满足条件的线程可以放到不同的集合中等待
与 synchronized 一样,都支持可重入
// 获取锁
reentrantLock.lock();
try {
// 临界区
} finally {
// 释放锁
reentrantLock.unlock();
}
可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
static ReentrantLock reentrantLock = new ReentrantLock();
public static void main(String[] args) {
try {
reentrantLock.lock();
m1();
} finally {
reentrantLock.unlock();
}
}
public static void m1() {
try {
reentrantLock.lock();
m2();
} finally {
reentrantLock.unlock();
}
}
public static void m2() {
try {
reentrantLock.lock();
} finally {
reentrantLock.unlock();
}
}
直接看例子:
这样有效避免死锁的发生。
public static void main(String[] args) throws InterruptedException {
ReentrantLock reentrantLock = new ReentrantLock();
Thread t1 = new Thread(() -> {
try {
/**
*
* 注意这里使用的是lockInterruptibly方法,如果使用lock.lock()方法,那么这里
* 等待的时候是不可以被打断的
*/
log.info("尝试获取锁");
reentrantLock.lockInterruptibly(); //被其它使用interrupt的线程打断
} catch (InterruptedException e) {
e.printStackTrace();
log.info("获取锁失败了");
// 这里如果出了错不要再往下执行了
return;
}
try {
log.info("获取到锁了没有被打断!");
} finally {
reentrantLock.unlock();
}
}, "t1");
reentrantLock.lock();
t1.start();
Thread.sleep(222);
// 打断线程thread,原本它的状态是在等待锁的,我们在它等待锁的时候打断了,不让它继续等待了
t1.interrupt();
log.info("主线程执行结束了 ");
}
直接看例子线程获取锁指定等待时间超过了就会别打断:
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
try {
//lock.tryLock指定时间获取不到锁就会释放,lock.lock获取不到锁会无限等待
if (!lock.tryLock(2, TimeUnit.SECONDS)) {//false
log.info("加锁失败了!");
// 这里如果出了错不要再往下执行了
return;
}
} catch (Exception e) {
log.info("被打断啦");
e.printStackTrace();
//执行到这里失败就不要继续执行了
return;
}
try {
log.info("执行完啦,获取到了锁,没被打断");
} finally {
lock.unlock();
}
}, "t1");
log.info("主线程获取锁");
lock.lock();
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("主线程释放锁");
lock.unlock();
log.info("线程运行结束");
}
使用锁超时解决哲学家就餐死锁问题:
@Slf4j
public class RepastTest {
public static void main(String[] args) {
Chopstick2 c1 = new Chopstick2("1");
Chopstick2 c2 = new Chopstick2("2");
Chopstick2 c3 = new Chopstick2("3");
Chopstick2 c4 = new Chopstick2("4");
Chopstick2 c5 = new Chopstick2("5");
new Philosopher2("苏格拉底", c1, c2).start();
new Philosopher2("柏拉图", c2, c3).start();
new Philosopher2("亚里士多德", c3, c4).start();
new Philosopher2("赫拉克利特", c4, c5).start();
new Philosopher2("阿基米德", c5, c1).start();
}
}
@Slf4j(topic = "Philosopher")
class Philosopher2 extends Thread{
Chopstick2 left;
Chopstick2 right;
public Philosopher2(String name, Chopstick2 left, Chopstick2 right) {
super(name);
this.left = left;
this.right = right;
}
private void eat() {
log.debug("eating...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
try {
if (left.tryLock(2, TimeUnit.SECONDS)){
try {
if (right.tryLock(2, TimeUnit.SECONDS)){
try {
eat();
}finally {
right.unlock();
}
}
}finally {
left.unlock();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Chopstick2 extends ReentrantLock {
private String name ;
public Chopstick2(String name) {
this.name = name;
}
@Override
public String toString() {
return "Chopstick{" +
"name='" + name + '\'' +
'}';
}
}
synchronized锁中,在entrylist等待的锁在竞争时不是按照先到先得来获取锁的,所以说synchronized锁时不公平的;ReentranLock锁默认是不公平的,但是可以通过设置实现公平锁。本意是为了解决之前提到的饥饿问题
,但是公平锁一般没有必要,会降低并发度,使用trylock也可以实现。
设置:只要把构造器设置为true即可
源码: /**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待
ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比
使用要点:
竞争 lock 锁成功后,从 await 后继续执行
static Lock lock = new ReentrantLock();
static Condition waitCigaretteQueue = lock.newCondition();
static Condition waitBreakfastQueue = lock.newCondition();
static volatile boolean hasCigarette = false;
static volatile boolean hasBreakfast = false;
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
lock.lock();
while (!hasCigarette) {
try {
waitBreakfastQueue.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("等到他的烟。");
} finally {
lock.unlock();
}
}, "等烟线程");
thread.start();
Thread thread2 = new Thread(() -> {
try {
lock.lock();
while (!hasBreakfast) {
try {
waitCigaretteQueue.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("等到早餐!");
}
} catch (Exception e) {
e.printStackTrace();
lock.unlock();
}
}, "等早餐线程");
thread2.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sendCigarette();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sendBreakfast();
}
public static void sendCigarette() {
try {
lock.lock();
log.info("烟来了");
hasCigarette = true;
waitCigaretteQueue.signal();
} finally {
lock.unlock();
}
}
public static void sendBreakfast() {
lock.lock();
try {
log.info("送早餐来了");
hasBreakfast = true;
waitBreakfastQueue.signal();
} finally {
lock.unlock();
}
}
固定运行顺序,比如,必须先 2 后 1 打印
public class OrderLock {
// 用来同步的对象
static Object obj = new Object();
// t2 运行标记, 代表 t2 是否执行
static boolean t2runed = false;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (obj) {
while (!t2runed) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(1);
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (obj) {
t2runed = true;
obj.notify();
}
System.out.println(2);
}, "t2");
t1.start();
t2.start();
}
}
/**
* wait 和 notify的缺点
* 首先,需要保证先 wait 再 notify,否则 wait 线程永远得不到唤醒。因此使用了『运行标记』来判断该不该
* wait
* 第二,如果有些干扰线程错误地 notify 了 wait 线程,条件不满足时还要重新等待,使用了 while 循环来解决
* 此问题
* 最后,唤醒对象上的 wait 线程需要使用 notifyAll,因为『同步对象』上的等待线程可能不止一个
*
* park 和 unpark 方法比较灵活,他俩谁先调用,谁后调用无所谓。并且是以线程为单位进行『暂停』和『恢复』,
* 不需要『同步对象』和『运行标记』,不存在一个多个线程同时等待一个对象锁的现象,因为每个线程调用park的结果是等待它自己线程的upark来解锁
*/
public class Test36 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try { Thread.sleep(1000); } catch (InterruptedException e) { }
// 当没有『许可』时,当前线程暂停运行;有『许可』时,用掉这个『许可』,当前线程恢复运行
LockSupport.park();
System.out.println("1");
});
Thread t2 = new Thread(() -> {
System.out.println("2");
// 给线程 t1 发放『许可』(多次连续调用 unpark 只会发放一个『许可』)
LockSupport.unpark(t1);
});
t1.start();
t2.start();
}
}
public class WaitNotify {
/**
* 使用notify和wait实现循环打印abcabc
* 交替打印ABC
* 输出内容: 等待标记: 下一个标记:
* A 1 2
* B 2 3
* C 3 1
*/
public static void main(String[] args) {
WaitNotifyDemo waitNotify = new WaitNotifyDemo(1, 15);
new Thread(() -> {
waitNotify.print("a", 1, 2);
}, "线程一").start();
new Thread(() -> {
waitNotify.print("b", 2, 3);
}, "线程二").start();
new Thread(() -> {
waitNotify.print("c", 3, 1);
}, "线程三").start();
}
}
class WaitNotifyDemo {
int flag;
int loopNumber;
public WaitNotifyDemo(int flag, int loopNumber) {
this.flag = flag;
this.loopNumber = loopNumber;
}
/**
* @param str 要打印的内容
* @param flag 线程的打印标记
* @param nextFlag 下一个打印标记
*/
public void print(String str, int flag, int nextFlag) {
for (int i = 0; i < loopNumber; i++) {
synchronized (this) {
while (this.flag != flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//标记相同则打印
System.out.println(str);
this.flag = nextFlag;
// 修改了打印标记,唤醒其它线程让他们抢啦!
this.notifyAll();
}
}
}
}
交替输出,线程 1 输出 a 5 次,线程 2 输出 b 5 次,线程 3 输出 c 5 次。现在要求输出 abcabcabcabcabc 怎么实现
wait notify 版 Test37.java
Lock 条件变量版
public class AwaitSignalDemo {
public static void main(String[] args) {
AwaitSignal awaitSignal = new AwaitSignal(15);
Condition aCondition = awaitSignal.newCondition();
Condition bCondition = awaitSignal.newCondition();
Condition cCondition = awaitSignal.newCondition();
new Thread(()->{
awaitSignal.print("a",aCondition,bCondition);
},"线程一").start();
new Thread(()->{
awaitSignal.print("b",bCondition,cCondition);
},"线程二").start();
new Thread(()->{
awaitSignal.print("c",cCondition,aCondition);
},"线程三").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
awaitSignal.lock();
try {
aCondition.signal();
}finally {
awaitSignal.unlock();
}
}
}
class AwaitSignal extends ReentrantLock {
int loopNUmber;
public AwaitSignal(int loopNUmber) {
this.loopNUmber = loopNUmber;
}
public void print(String str, Condition current, Condition next) {
for (int i = 0; i < loopNUmber; i++) {
this.lock();
try {
try {
current.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(str);
next.signal();
} finally {
this.unlock();
}
}
}
}
public class Test39 {
static Thread thread2 ;
static Thread thread1;
static Thread thread3;
public static void main(String[] args) {
ParkUnpark parkUnpark = new ParkUnpark(15);
thread1 = new Thread(() -> {
parkUnpark.print("a",thread2);
}, "线程一");
thread2 = new Thread(() -> {
parkUnpark.print("b",thread3);
}, "线程二");
thread3 = new Thread(() -> {
parkUnpark.print("c",thread1);
}, "线程三");
thread1.start();
thread2.start();
thread3.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.unpark(thread1);
}
}
class ParkUnpark{
int loopNumber;
public ParkUnpark(int loopNumber) {
this.loopNumber = loopNumber;
}
public void print(String str , Thread next ){
for (int i=0;i<loopNumber;i++){
LockSupport.park();
System.out.print(str);
LockSupport.unpark(next);
}
}
}