并发编程-共享模型之管程2
4.11多把锁
多把不相干的锁
一间大屋子有2个功能:睡觉、学习、互不相干
现在小南要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低
解决方法是准备多个房间(多个锁对象)
实现:
public class Test7 {
public static void main(String[] args) {
Bigroom bigroom = new Bigroom();
new Thread(()->{
try {
bigroom.study();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"小南").start();
new Thread(()->{
try {
bigroom.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"小女").start();
}
}
@Slf4j
class Bigroom{
private final Object studyRoom= new Object();
private final Object bedRoom= new Object();
public void sleep() throws InterruptedException {
synchronized (bedRoom){
log.info("sleep 2小时");
Thread.sleep(2);
}
}
public void study() throws InterruptedException {
synchronized (studyRoom){
log.info(" study 1小时");
Thread.sleep(1);
}
}
}
将锁的粒度细分
好处,是可以增强并发度
坏处,如果一个线程需要同时获得多把锁,就容易发生死锁
4.12活跃性
死锁
有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁
t1线程获得 A对象 锁,接下来想获取 B对象 的锁
t2线程获得 B对象 锁,接下来想获取 A对象 的锁
@Slf4j
public class DeadLock {
public static void main(String[] args) {
test1();
}
private static void test1(){
Object A = new Object();
Object B = new Object();
Thread t1= new Thread(()->{
synchronized (A){
log.info("lock A");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B){
log.info("lock B");
log.info("操作。。。");
}
}
},"t1");
Thread t2= new Thread(()->{
synchronized (B){
log.info("lock B");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (A){
log.info("lock A");
log.info("操作。。。");
}
}
},"t2");
t1.start();
t2.start();
}
}
定位死锁
检测死锁可以使用jconsole工具,或者使用jps定位进程id,再用jstack定位死锁
使用jps

使用jconsole工具

哲学家就餐问题

活锁
活锁出现在2个线程互相改变对方的结束条件,最后谁也无法结束,例如

饥饿

顺序加锁的解决方案可能导致饥饿(部分线程的到锁的机会太低)

4.13ReentrantLock
相对于synchronized它具备如下特点
可中断
可以设置超时时间
可以设置公平锁(防止饥饿)
支持多个条件变量(monitor中的waitset,synchronized只支持一个,ReentrantLock可以根据条件到不同的waitset中等待)
与synchronized一样,都支持可重入 (线程对同一个对象反复加锁)
基本语法
//获取锁
reentrantLock.lock();
try{
//临界区
}finally{
//释放锁
reentrantLock.unlock();
}
可重入
可重入是指同一个线程如果首次获得了这把锁,那么因为它是这本锁的拥有者,因为有权力再次获取这把锁
如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
@Slf4j
public class test8 {
private static ReentrantLock lock=new ReentrantLock();
public static void main(String[] args) {
lock.lock();
try {
log.info("main..");
m1();
} finally {
lock.unlock();
}
}
public static void m1(){
lock.lock();
try {
log.info("m1..");
m2();
} finally {
lock.unlock();
}
}
public static void m2(){
lock.lock();
try {
log.info("m2..");
} finally {
lock.unlock();
}
}
}
可打断
ReentrantLock中有可打断的机制(方法)
ReentrantLock.lockInterruptibly()可以被打断
ReentrantLock.lock()不可以被打断
@Slf4j
public class test9 {
private static ReentrantLock lock=new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
//如果没有竞争那么此方法就会获取lock对象锁
//如果有竞争就会进入阻塞队列,可以被其他线程用interrupt方法打断
log.info("尝试获取锁");
lock.lockInterruptibly();// lock.lock()方法不能被打断
} catch (InterruptedException e) {
e.printStackTrace();
log.info("没有获取锁");
return;
}
try {
log.info("获取到锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
t1.start();
Thread.sleep(1);
log.info("主线程打断t1线程");
t1.interrupt();
}
}
锁超时
@Slf4j
public class test10 {
private static ReentrantLock lock=new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
log.info("尝试获得锁");
try {
//获取锁,尝试等待1s,获取不到,lock.tryLock()返回false
if (!lock.tryLock(1, TimeUnit.SECONDS)) {
log.info("没有获得锁");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
//tryLock方法可以被打断
return;
}
try {
log.info("获得到锁");
} finally {
lock.unlock();
}
},"t1");
lock.lock();
t1.start();
Thread.sleep(1);
log.info("主线程释放锁");
lock.unlock();
}
}
公平锁
synchronized是不公平的,不会按照阻塞队列的顺序来获取,而是一起争抢锁
ReentrantLock默认是不公平的,但是可以通过构造方法,传入true,来保证公平性,公平锁一般没有必要,会降低并发度,后面有原理分析
条件变量
synchronized中也有条件变量,就是讲解原理时哪个waitSet休息室,当条件不满足时进入waitSet等待
ReentrantLock的条件变量比synchronized的强大之处在于,它是支持多个条件变量的,区别:
synchronized是那些不满足条件的线程都在一间休息室等待
而ReentrantLock支持多间休息室,唤醒时按休息室唤醒,不会影响其他的休息室
使用流程
await前需要获得锁
await执行后,会释放锁,进入conditionObject等待
await的线程被唤醒(或打断、或超时)取重新竞争lock锁
竞争lock锁成功后,从await后继续执行
@Slf4j
public class test11 {
private static boolean yan=false;
private static boolean waiMai=false;
private static ReentrantLock lock=new ReentrantLock();
//等待烟的休息室
private static Condition waitYan=lock.newCondition();
//等待外卖的休息室
private static Condition waitWaiMai=lock.newCondition();
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
log.info("有烟没,{}",yan);
lock.lock();
try {
while (!yan){
log.info("没有烟,休息一下");
try {
waitYan.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("有烟,可以开始干活");
} finally {
lock.unlock();
}
},"小南").start();
new Thread(()->{
log.info("外卖送达没,{}",waiMai);
lock.lock();
try {
while (!waiMai){
log.info("没有外卖,休息一下");
try {
waitWaiMai.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("有外卖,可以开始干活");
} finally {
lock.unlock();
}
},"小女").start();
Thread.sleep(1);
new Thread(()->{
lock.lock();
try{
//改变条件变量再叫醒
waiMai=true;
waitWaiMai.signal();
}finally {
lock.unlock();
}
},"送外卖线程").start();
new Thread(()->{
lock.lock();
try{
//改变条件变量再叫醒
yan=true;
waitYan.signal();
}finally {
lock.unlock();
}
},"送烟线程").start();
}
}
同步模式之顺序控制
固定运行顺序
先打印2后打印1
用synchronized
@Slf4j
public class test12 {
private static boolean flag=false;
private static Object obj= new Object();
public static void main(String[] args) {
new Thread(()->{
synchronized (obj){
while (!flag){
log.info("进入等待");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("打印1");
}
},"t1").start();
new Thread(()->{
synchronized (obj){
log.info("打印2");
flag=true;
obj.notifyAll();
}
},"t2").start();
}
}
用ReentrantLock
@Slf4j
public class test13 {
private static boolean flag=false;
private static ReentrantLock lock=new ReentrantLock();
private static Condition condition1=lock.newCondition();
public static void main(String[] args) {
new Thread(()->{
lock.lock();
try {
while (!flag){
log.info("进入等待");
try {
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("打印1");
} finally {
lock.unlock();
}
},"t1").start();
new Thread(()->{
lock.lock();
try {
log.info("打印2");
flag=true;
condition1.signalAll();
} finally {
lock.unlock();
}
},"t2").start();
}
}
用park方法和unpark方法
@Slf4j
public class test14 {
public static void main(String[] args) {
Thread t1=new Thread(()->{
LockSupport.park();
log.info("打印1");
},"t1");
t1.start();
new Thread(()->{
LockSupport.unpark(t1);
log.info("打印2");
},"t2").start();
}
}
交替输出
线程1输出a 5次,线程2输出b 5次,线程3输出c 5次。现在要求输出abc abc abc abc abc
用synchronized
@Slf4j
public class test15 {
public static void main(String[] args) {
WaitNotify wn = new WaitNotify(1, 5);
new Thread(() -> {
wn.print("a", 1, 2);
}, "a").start();
new Thread(() -> {
wn.print("b", 2, 3);
}, "b").start();
new Thread(() -> {
wn.print("c", 3, 1);
}, "c").start();
}
}
/**
* 输出内容 等待标记 下一个标记
* a 1 2
* b 2 3
* c 3 1
*/
class WaitNotify {
public void print(String str, int waitFlag, int nextFlag) {
for (int i = 0; i < loopNum; i++) {
synchronized (this) {
while (flag != waitFlag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(str);
flag = nextFlag;
this.notifyAll();
}
}
}
//等待标记
private int flag;
//循环次数
private int loopNum;
public WaitNotify(int flag, int loopNum) {
this.flag = flag;
this.loopNum = loopNum;
}
}
用ReentrantLock

用park、unpark

本章小结

1.monitor是给synchronized配合使用,是基于jvm实现,底层是c++
2.ReentrantLock是java层面的monitor
3.保护性暂停模式,是2个线程传递结果的
4.生产者消费者模式,结果产生者和结果消费者不是一一对应的关系
5.顺序控制,控制线程执行的顺序