并发编程(三)---共享模型之管程

三、共享模型之管程

3.1临界区(Critical Section)

  • 一个程序运行多个线程本身是没有问题的
  • 问题出在多个线程访问共享资源
    • 多个线程读取共享资源其实也没有问题
    • 在多个线程对共享资源读写操作时发生指令交错,就会出现问题
  • 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区

例如如下的临界区

static int counter = 0;

static void increment(){
    counter++;
}

static void decrement(){
    counter--;
}

3.2synchronized解决方案

互斥

为了避免临界区的竞态条件发生,有多种手段可以达到目的

  • 阻塞式的解决方案:synchronized、Lock
  • 非阻塞式的解决方案:原子变量

synchronized,俗称【对象锁】

本次课使用阻塞式的解决方案: synchronized,来解决上述问题,即俗称的【对象锁】,它采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换

注意:虽然java中互斥和同步都可以采用synchronized关键字来完成,但它们还是有区别的

  • 同步是由于线程执行的先后、顺序不同、需要一个线程等待其它线程运行到某个点
  • 同步是由于线程执行的先后、顺序不同、需要一个线程等待其它线程运行到某个点
3.2.1synchronized

语法

synchronized(对象){   //线程1、线程2(blocked)
    临界区
}

实例

@Slf4j
public class SynchronizedTest {
    static int count = 0;
    static Object object = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (object){
                    count++;
                }
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (object){
                    count--;
                }
            }
        });

        t1.start();
        t2.start();
        log.debug("{}",count);
    }

运行结果

08:18:53.134 [main] DEBUG com.example.Test.SynchronizedTest - 0

可以看到,运行结果为0,说明加锁之后可以确保在一个线程执行的时候,另一个线程会被阻塞,所以无论运行多少次,结果都是0,但是如果不加锁,那么运行结果会有很多种结果

思考

synchronized实际是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断

为了加深理解:

  • 如果把synchronized(obj)放在for循环外面,如何理解

    • 可以
  • 如果把t1 synchronized(obj1) 而 t2 synchronized(obj2) 会怎么样运作?

    • 不会,必须是同一个对象
  • 如果t1 synchronize(obj) 而t2没有加会怎么样?如何理解?

    • 不会,数据仍然不对应

3.3方法上的synchronized

class Test{
    public synchronized void test(){
        
    }
}

//等价于

class Test{
    public void test(){
        synchronized(this){
            
        }
    }
}
class Test{
    public synchronized static void test(){
        
    }
}

//等价于

class Test{
    public static void test(){
        synchronized(this){
            
        }
    }
}

上面的案例优化

@Slf4j
public class SynchronizedTest {
    public static void main(String[] args) throws InterruptedException {
        Room room = new Room();
        Thread t1 = new Thread(() -> {
            log.debug("{}","线程1run");
            for (int i = 0; i < 5000; i++) {
                room.add();
            }
        });

        Thread t2 = new Thread(() -> {
            log.debug("线程2run");
            for (int i = 0; i < 5000; i++) {
                room.decrease();
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("{}",room.getCount());
    }
}

class Room{
    private int count = 0;
    public synchronized void add(){
        count++;
    }

    public synchronized void decrease(){
        count--;
    }

    public synchronized int getCount(){
        return count;
    }

}

运行结果

08:46:26.954 [Thread-0] DEBUG com.example.Test.SynchronizedTest - 线程1run
08:46:26.954 [Thread-1] DEBUG com.example.Test.SynchronizedTest - 线程2run
08:46:26.958 [main] DEBUG com.example.Test.SynchronizedTest - 0
“线程八锁”

其实就是考察synchronized锁住的是哪个对象

案例1 互斥 12或21

@Slf4j
class Number{
    public synchronized void a(){
        log.debug("1");
    }
    public synchronized void b(){
        log.debug("2");
    }
}

public static void main (String[] args){
    Number n1 = new Number();
    new Thread(()->{ n1.a(); }).start();
    new Thread(()->{ n1.a(); }).start();
}

结果

07:46:46.556 [Thread-0] DEBUG com.example.EightSynchroizedDemo.Number - 1
07:46:46.558 [Thread-1] DEBUG com.example.EightSynchroizedDemo.Number - 2

锁住的是同一个对象(this) 所以会有互斥,可能是线程1先执行,也有可能是2先执行

案例2 互斥 12或21

@Slf4j
class Number{
    public synchronized void a(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("1");
    }
    public synchronized void b(){
        log.debug("2");
    }
}

 public static void main(String[] args) {
        Number n1 = new Number();
        new Thread(()->{ n1.a(); }).start();
        new Thread(()->{ n1.b(); }).start();
  }

结果

07:49:30.848 [Thread-0] DEBUG com.example.EightSynchroizedDemo.Number - 1
07:49:30.852 [Thread-1] DEBUG com.example.EightSynchroizedDemo.Number - 2

同案例一,看谁先调度

案例3

@Slf4j
class Number{
    public synchronized void a(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("1");
    }
    public synchronized void b(){
        log.debug("2");
    }

    public void c(){
        log.debug("3");
    }
}

 public static void main(String[] args) {
        Number n1 = new Number();
        new Thread(()->{ n1.a(); }).start();
        new Thread(()->{ n1.b(); }).start();
        new Thread(()->{ n1.c(); }).start();
  }

结果

07:51:46.658 [Thread-2] DEBUG com.example.EightSynchroizedDemo.Number - 3
07:51:47.660 [Thread-0] DEBUG com.example.EightSynchroizedDemo.Number - 1
07:51:47.660 [Thread-1] DEBUG com.example.EightSynchroizedDemo.Number - 2
//3 1s 12
//23 1s 1
//32 1s 1

线程3和其他两个线程并行执行,但是线程1和线程2互斥

案例4

@Slf4j
class Number{
    public synchronized void a(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("1");
    }
    public synchronized void b(){
        log.debug("2");
    }
}
 public static void main(String[] args) {
        Number n1 = new Number();
        Number n2 = new Number();
        new Thread(()->{ n1.a(); }).start();
        new Thread(()->{ n2.b(); }).start();
  }

结果

07:56:06.328 [Thread-1] DEBUG com.example.EightSynchroizedDemo.Number - 2
07:56:07.340 [Thread-0] DEBUG com.example.EightSynchroizedDemo.Number - 1

锁住的对象不同,无互斥,所以总是线程2先执行,因为线程1睡眠1s

案例5

@Slf4j
class Number{
    public static synchronized void a(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("1");
    }
    public synchronized void b(){
        log.debug("2");
    }
}
public static void main(String[] args) {
        Number n1 = new Number();
        new Thread(()->{ n1.a(); }).start();
        new Thread(()->{ n1.b(); }).start();
  }

结果

07:58:07.288 [Thread-1] DEBUG com.example.EightSynchroizedDemo.Number - 2
07:58:08.282 [Thread-0] DEBUG com.example.EightSynchroizedDemo.Number - 1

同案例5,但是a方法加了static,所以此时a方法实际上锁住的是类对象,但是此时都是n1对象调用的a方法和b方法。

案例6

@Slf4j
class Number{
    public static synchronized void a(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("1");
    }
    public static synchronized void b(){
        log.debug("2");
    }
}
public static void main(String[] args) {
        Number n1 = new Number();
//        Number n2 = new Number();
        new Thread(()->{ n1.a(); }).start();
        new Thread(()->{ n1.b(); }).start();

}

结果

08:00:53.791 [Thread-0] DEBUG com.example.EightSynchroizedDemo.Number - 1
08:00:53.797 [Thread-1] DEBUG com.example.EightSynchroizedDemo.Number - 2

此时a方法和b方法都是对类对象加锁,但是类对象只有一份,所以此时两线程会互斥,看谁先被调度

案例7

@Slf4j
class Number{
    public static synchronized void a(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("1");
    }
    public  synchronized void b(){
        log.debug("2");
    }
}
public static void main(String[] args) {
        Number n1 = new Number();
        Number n2 = new Number();
        new Thread(()->{ n1.a(); }).start();
        new Thread(()->{ n2.b(); }).start();
}

结果

08:03:00.603 [Thread-1] DEBUG com.example.EightSynchroizedDemo.Number - 2
08:03:01.605 [Thread-0] DEBUG com.example.EightSynchroizedDemo.Number - 1

不互斥,a方法是类对象,b方法是this对象,通过不同的对象实例调用,不是对同一个方法生效,所以不互斥

案例8

@Slf4j
class Number{
    public static synchronized void a(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("1");
    }
    public static synchronized void b(){
        log.debug("2");
    }
}
public static void main(String[] args) {
        Number n1 = new Number();
        Number n2 = new Number();
        new Thread(()->{ n1.a(); }).start();
        new Thread(()->{ n2.b(); }).start();
}

结果

08:06:45.100 [Thread-0] DEBUG com.example.EightSynchroizedDemo.Number - 1
08:06:45.104 [Thread-1] DEBUG com.example.EightSynchroizedDemo.Number - 2

a方法和b方法都是锁住类对象,但是类对象只有一个,此时在运行时,即使调用对象不同,但是也是互斥。

说明

用static修饰的加锁方法,为什么锁住的是类对象,可以这样理解,在调用静态方法时,可以通过对象名.方法名(Number.a())调用,静态方法的调用基本都是这样,所以没吃调用静态方法实际上都是对象名.方法名(Number.a()),长的是不是都一样,只是后面方法名不通过罢了,所以静态变量锁会互斥

3.4变量的线程安全分析

3.4.1成员变量和静态变量是否线程安全
  • 如果没共享,那么安全

  • 如果被共享,根据他们状态是否能改变,有分两种情况

    • 如果只有读操作,则线程安全

    • 如果有读写操作,则这段代码是临界区,需要考虑线程安全

3.4.2局部变量是否线程安全?
  • 局部变量是线程安全的
  • 但局部变量引用的对象则未必
    • 如果该对象没有逃离方法的作用访问,它是线程安全的
    • 如果该对象逃离方法的作用范围,需要考虑线程安全
3.4.3常见线程安全类
  • String
  • Integer
  • StringBuffer
  • Random
  • Vector
  • Hashtable
  • java.util.concurrent包下的类

这里说的线程安全是指,多个线程调用他们同一个实例的某个方法时,是线程安全的,也可以理解为

  • 他们的每个方法都是原子的
  • 但注意他们多个方法的组合不是原子的,如果想要保证继续线程安全,还是需要上锁

3.5卖票练习

@Slf4j
public class SellTest {
    private static Random random = new Random();

    public static void main(String[] args) throws InterruptedException {
        TicketWindow ticketWindow = new TicketWindow(1000);

        List<Thread> list = new ArrayList<>();

        List<Integer> amountList = new Vector<>();

        for (int i = 0; i < 4000; i++) {
            Thread thread = new Thread(()->{
                int sell = ticketWindow.sell(random(5));
                amountList.add(sell);
            });
            list.add(thread);
            thread.start();
        }

        for (Thread thread : list) {
            thread.join();
        }
        log.debug("余票{}",ticketWindow.getCount());
        log.debug("卖出的票数{}",amountList.stream().mapToInt(i -> i).sum());

    }
    public static int random(int amount){return random.nextInt(5)+1;}
}
class TicketWindow{
    private int count;

    public TicketWindow(int count) {
        this.count = count;
    }

    public int getCount() {
        return count;
    }

    //售票
    public int sell(int amount){
        if(this.count >= amount){
            this.count-=amount;
            return amount;
        }else {
            return 0;
        }
    }
}

运行结果

20:30:51.321 [main] DEBUG com.example.demo.SellTest - 余票0
20:30:51.330 [main] DEBUG com.example.demo.SellTest - 卖出的票数1000

20:32:11.534 [main] DEBUG com.example.demo.SellTest - 余票0
20:32:11.541 [main] DEBUG com.example.demo.SellTest - 卖出的票数1004

很显然,第二种情况卖出的票数超过了总量1000,造成这样原因的就是因为,多个线程一起访问时,没有加锁,所以对同一个资源进行访问,使得结果有误。

怎么优化呢?

其实就是在sell()方法是加上synchronized即可

 //售票
    public synchronized int sell(int amount){
        if(this.count >= amount){
            this.count-=amount;
            return amount;
        }else {
            return 0;
        }
    }

因为都是对售票操作,所以加在这即可,运行结果中每个都是1000

3.6Monitor(锁)概念

Monitor被翻译为监视器或者管理

每个Java对象都可以关联一个Monitor对象,如果使用synchronized给对象(重量级)之后,该对象头的Mark Word中就被设置指向Monitor对象的指针

Monitor结构如下

在这里插入图片描述

  • 刚开始Monitor中Owner为null
  • 当Thread-2执行synchronized(obj)将会将Monitor的所有者Owner置为Thread-2,Monitor中只能有一个Owner
  • 在Thread-2上锁的过程中,如果Thread-3,Thread-4,Thread-5也来执行synchronized(obj),就会进入EntryList BLOCKED

3.7synchronized原理进阶

3.7.1轻量级锁

应用场景

如果一个对象虽然有多线程访问,但多线程访问的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化

轻量级锁对使用者是透明的,即语法仍然是synchronized

假设有两个方法同步块,利用同一个对象加锁

static final Object obj = new Object();
public static void method1(){
    synchronized(obj){
        //同步块	A
        method2();
    }
}
public static void method2(){
    synchronized(obj){
        //同步块	B
        
    }
}
3.7.2锁膨胀

如果在尝试加轻量级锁的过程中,CAS操作无法成功,这时一种情况就是有其他线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁

static Object obj = new Object();
public static void method1(){
    synchronized(obj){
        //同步块
    }
}
3.7.3自旋优化

重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞

什么是自旋?

自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务,使用这种锁会造成busy-waiting,适用于多核cpu

自旋重试成功的情况

线程1(cpu1上)对象Mark线程2(cpu2上)
-10(重量锁)-
访问同步块,获取monitor10(重量锁)重置锁指针-
成功(加锁)10(重量锁)重置锁指针-
执行同步块10(重量锁)重置锁指针-
执行同步块10(重量锁)重置锁指针访问同步块,获取monitor
执行同步块10(重量锁)重置锁指针自旋重试
执行完毕10(重量锁)重置锁指针自旋重试
成功(解锁)01(无锁)自旋重试
-10(重量锁)重置锁指针成功(加锁)
-10(重量锁)重置锁指针执行同步块
-

自旋失败的情况

线程1(cpu1上)对象Mark线程2(cpu2上)
-10(重量锁)-
访问同步块,获取monitor10(重量锁)重置锁指针-
成功(加锁)10(重量锁)重置锁指针-
执行同步块10(重量锁)重置锁指针-
执行同步块10(重量锁)重置锁指针访问同步块,获取monitor
执行同步块10(重量锁)重置锁指针自旋重试
执行同步块10(重量锁)重置锁指针自旋重试
执行同步块10(重量锁)重置锁指针自旋重试
执行同步块10(重量锁)重置锁指针自旋重试
执行同步块10(重量锁)重置锁指针阻塞
-

注意

  • 在java6之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认定为这次自旋成功的可能性会高,就会多自旋几次;反之,就少自旋甚至不自旋。
  • 自旋会占用CPU时间,单核CPU自旋就是浪费资源,多核CPU自旋才能发挥优势
  • java7之后不能控制是否开启自旋功能
3.7.4偏向锁

轻量级锁在竞争时(就自己这个线程),每次重入仍然需要执行CAS操作

java6中引入了偏向锁来做进一步优化:只有第一次使用CAS将线程id设置到对象的Mark Word头,之后发现这个线程id是自己的就表示没有竞争,不用重新CAS。以后只要不发生竞争,这个对象就归该线程所有

CAS概念

CAS的全称是 Compare-and-Swap,也就是比较并交换,是并发编程中一种常用的算法。它包含了三个参数:V,A,B。
其中,V表示要读写的内存位置,A表示旧的预期值,B表示新值
CAS指令执行时,当且仅当V的值等于预期值A时,才会将V的值设为B,如果V和A不同,说明可能是其他线程做了更新,那么当前线程就什么都不做,最后,CAS返回的是V的真实值。
而在多线程的情况下,当多个线程同时使用CAS操作一个变量时,只有一个会成功并更新值,其余线程均会失败,但失败的线程不会被挂起,而是不断的再次循环重试。正是基于这样的原理,CAS即时没有使用锁,也能发现其他线程对当前线程的干扰,从而进行及时的处理。

3.7.5锁消除

3.8wait notify

  • wait():使调用该方法的线程释放共享资源锁,然后从运行状态退出,进入等待队列,直到被再次唤醒。
  • wait(long):超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回。
  • wait(long,int):对于超时时间更细力度的控制,单位为纳秒。
  • notify():随机唤醒等待队列中等待同一共享资源的一个线程,并使该线程退出等待队列,进入可运行状态,也就是notify()方法仅通知一个线程。
  • notifyAll():使所有正在等待队列中等待同一共享资源的全部线程退出等待队列,进入可运行状态。此时,优先级最高的那个线程最先执行,但也有可能是随机执行,这取决于JVM虚拟机的实现。
3.8.1原理

在这里插入图片描述

  • Owner线程发现条件不满足,调用wait方法,即可进入WaitSet变为WAITING状态
  • BLOCKED和WAITING的线程都处于阻塞状态,不占用CPU时片
  • BLOCKED线程会在Owner线程释放锁时唤醒
  • WAITING线程会在Owner线程调用notify或notifyAll时唤醒,但唤醒后并不意味着立刻获得锁,仍然需要进入EntryList重新竞争
3.8.2API介绍
  • obj.wait()让进入object监视器的线程到waitSet等待
  • obj.notify()在object上正在waitSet等待的线程中挑一个唤醒
  • obj.notifyAll()让object上正在waitSet等待的线程全部唤醒

他们都是线程之间进行协作的手段,都属于Object对象的方法,必须获得此对象的锁,才能调用这几个方法

@Slf4j
public class demo01 {
    final static Object obj = new Object();

    public static void main(String[] args) {
        new Thread(()->{
            synchronized (obj){
                log.debug("执行....");
                try {
                    obj.wait();   //线程一直等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其他代码...");
            }
        },"t1").start();

        new Thread(()->{
            synchronized (obj){
                log.debug("执行....");
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其他代码...");
            }
        },"t2").start();

        //主线程休息2s
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("唤醒obj上的其他线程");
        synchronized (obj){
            obj.notify(); //唤醒obj上的一个线程
//            obj.notifyAll(); //唤醒obj上的所有线程
        }
    }
}

运行结果

//obj.notify();
20:50:01.174 [t1] DEBUG com.example.demo.demo01 - 执行....
20:50:01.176 [t2] DEBUG com.example.demo.demo01 - 执行....
20:50:03.174 [main] DEBUG com.example.demo.demo01 - 唤醒obj上的其他线程
20:50:03.174 [t1] DEBUG com.example.demo.demo01 - 其他代码...
//注意此时方法并未结束,因为t2线程还没被唤醒

// obj.notifyAll();
//20:51:18.327 [t1] DEBUG com.example.demo.demo01 - 执行....
//20:51:18.330 [t2] DEBUG com.example.demo.demo01 - 执行....
//20:51:20.338 [main] DEBUG com.example.demo.demo01 - 唤醒obj上的其他线程
//20:51:20.338 [t2] DEBUG com.example.demo.demo01 - 其他代码...
//20:51:20.338 [t1] DEBUG com.example.demo.demo01 - 其他代码...
//此时方法结束了

wait() 方法会释放对象的锁,进入WaitSet等待区,从而让其他线程就有机会获取对象的锁。无限制等待,知道notify为止

wait(long n)有时间限制的等待,到n毫秒之后结束等待,或是被notify

3.9wait notify的正确使用姿势

sleep(long n)和wait(long n)的区别

1)sleep是Thread方法,而wait是Object的方法

2)sleep不需要强制和synchronized配合使用,但wait需要和synchronized配合使用

3)sleep在睡眠期间,不会释放对象锁的,但wait在等待的时机会释放对象锁

4)他们的状态都是TIMED_WAITING,都是有时限的等待

package com.example.demo;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class demo02 {
    static final Object room = new Object();
    static  boolean hasCigarette = false;
    static  boolean hasTakeout = false;

    public static void main(String[] args) {
      new Thread(()->{
         synchronized (room){
             log.debug("有烟没?[{}]",hasCigarette);
             if (!hasCigarette){
                 log.debug("没烟,先歇会");
                 try {
                     room.wait();
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
             log.debug("有烟没?[{}]",hasCigarette);
             if(hasCigarette){
                 log.debug("可以开始干活了");
             }else {
                 log.debug("没干成活");
             }
         }
      },"小南").start();

      new Thread(()->{
         synchronized (room){
             log.debug("外卖送到没?[{}]",hasTakeout);
             if (!hasTakeout){
                 log.debug("外卖没送到,先歇会");
                 try {
                     room.wait();
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
             log.debug("外卖送到没?[{}]",hasTakeout);
             if(hasTakeout){
                 log.debug("可以开始干活了");
             }else {
                 log.debug("没干成活");
             }
         }
      },"小女").start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            synchronized (room){
                hasTakeout = true;
                log.debug("外卖到了哦!");
                room.notifyAll();
            }
        },"送外卖的").start();
    }
}

运行结果:

15:05:57.882 [小南] DEBUG com.example.demo.demo02 - 有烟没?[false]
15:05:57.887 [小南] DEBUG com.example.demo.demo02 - 没烟,先歇会
15:05:57.887 [小女] DEBUG com.example.demo.demo02 - 外卖送到没?[false]
15:05:57.887 [小女] DEBUG com.example.demo.demo02 - 外卖没送到,先歇会
15:05:58.889 [送外卖的] DEBUG com.example.demo.demo02 - 外卖到了哦!
15:05:58.889 [小女] DEBUG com.example.demo.demo02 - 外卖送到没?[true]
15:05:58.889 [小女] DEBUG com.example.demo.demo02 - 可以开始干活了
15:05:58.890 [小南] DEBUG com.example.demo.demo02 - 有烟没?[false]
15:05:58.890 [小南] DEBUG com.example.demo.demo02 - 没干成活

可以看到,使用if判断时,如果当叫醒时,如果叫醒的不是需要的线程(虚假唤醒),那么仍然会处于等待,干不成活。

所以为了确保能正确唤醒需要的线程,其他的线程被唤醒后任然需要处于等待,所以if可以改为while

更改之后

package com.example.demo;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class demo02 {
    static final Object room = new Object();
    static  boolean hasCigarette = false;
    static  boolean hasTakeout = false;

    public static void main(String[] args) {
      new Thread(()->{
         synchronized (room){
             log.debug("有烟没?[{}]",hasCigarette);
             //更改if->while
             while (!hasCigarette){
                 log.debug("没烟,先歇会");
                 try {
                     room.wait();
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
             log.debug("有烟没?[{}]",hasCigarette);
             if(hasCigarette){
                 log.debug("可以开始干活了");
             }else {
                 log.debug("没干成活");
             }
         }
      },"小南").start();

      new Thread(()->{
         synchronized (room){
             log.debug("外卖送到没?[{}]",hasTakeout);
             //更改if->while
             while (!hasTakeout){
                 log.debug("外卖没送到,先歇会");
                 try {
                     room.wait();
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
             log.debug("外卖送到没?[{}]",hasTakeout);
             if(hasTakeout){
                 log.debug("可以开始干活了");
             }else {
                 log.debug("没干成活");
             }
         }
      },"小女").start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            synchronized (room){
                hasTakeout = true;
                log.debug("外卖到了哦!");
                room.notifyAll();
            }
        },"送外卖的").start();
    }
}

运行结果:

15:11:00.461 [小南] DEBUG com.example.demo.demo02 - 有烟没?[false]
15:11:00.464 [小南] DEBUG com.example.demo.demo02 - 没烟,先歇会
15:11:00.464 [小女] DEBUG com.example.demo.demo02 - 外卖送到没?[false]
15:11:00.465 [小女] DEBUG com.example.demo.demo02 - 外卖没送到,先歇会
15:11:01.464 [送外卖的] DEBUG com.example.demo.demo02 - 外卖到了哦!
15:11:01.464 [小女] DEBUG com.example.demo.demo02 - 外卖送到没?[true]
15:11:01.464 [小女] DEBUG com.example.demo.demo02 - 可以开始干活了
15:11:01.464 [小南] DEBUG com.example.demo.demo02 - 没烟,先歇会

//此时程序未结束,仍然处于等待中,需要被唤醒
synchronized(lock){
    while(条件不成立){
        lock.wait();
    }
    //干活
}

//另一个线程
synchronized(lock){
    lock.notifyAll();
}

3.10 Park & Unpark

3.10.1基本使用
//暂停当前线程
LockSupport.park();

//恢复某个线程的运行
LockSupport.unpark(暂停线程对象);

简单使用

package com.example.MessageQueue;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.LockSupport;

/**
 * @author 我见青山多妩媚
 * @date Create on 2022/7/2 16:30
 */
@Slf4j
public class Demo22 {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            log.debug("start..");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("park...");
            //实际上是wait 
            LockSupport.park();
            
            log.debug("resume...");
        },"t1");
        t1.start();
        
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("unpark...");
        LockSupport.unpark(t1);
    }
}

运行结果:

16:32:47.766 [t1] DEBUG com.example.MessageQueue.Demo22 - start..
16:32:48.776 [t1] DEBUG com.example.MessageQueue.Demo22 - park...
16:32:49.769 [main] DEBUG com.example.MessageQueue.Demo22 - unpark...
16:32:49.769 [t1] DEBUG com.example.MessageQueue.Demo22 - resume...

当先调用unpark,再调用park时,运行结果如下:

package com.example.MessageQueue;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.LockSupport;

/**
 * @author 我见青山多妩媚
 * @date Create on 2022/7/2 16:30
 */
@Slf4j
public class Demo22 {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            log.debug("start..");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("park...");
            LockSupport.park();

            log.debug("resume...");
        },"t1");
        t1.start();

//        try {
//            Thread.sleep(1000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        log.debug("unpark...");
        LockSupport.unpark(t1);
    }
}

15:23:07.926 [main] DEBUG com.example.MessageQueue.Demo22 - unpark...
15:23:07.926 [t1] DEBUG com.example.MessageQueue.Demo22 - start..
15:23:08.942 [t1] DEBUG com.example.MessageQueue.Demo22 - park...
15:23:08.942 [t1] DEBUG com.example.MessageQueue.Demo22 - resume...

这是为什么?不是先调用的unpark吗?

3.10.2特点

与Object的wait & notify相比

  • wait,notify和notifyAll必须配合Object Monitor一起使用,而unpark不必
  • park & unpark是以线程为单位来阻塞和唤醒线程,而notify只能随机唤醒一个等待线程,notifyAll是唤醒所有等待线程,就没有那么精确
  • park & unpark 可以先unpark,而wait & notify不能先notify
3.10.3原理

每个线程都有自己的一个Parker对象,由三部分组成_counter、_cond、_mutex。比如:

  • 线程就像一个旅人,Parker就像他随身携带的背包,条件变量就好像背包中的帐篷,_counter就好比背包中的备用干粮(0为耗尽,1为充足)
  • 调用park就是看需不需要停下来休息
    • 如果备用干粮耗尽,那么需要进帐篷休息
    • 如果没有干粮充足,那么不需要停留,继续前进
  • 调用unpark,就好比让干粮充足
    • 如果这时线程还在帐篷(park时),那么就唤醒他让他前进
    • 如果这时线程还在运行,那么下次他调用park时,仅仅是消耗掉备用干粮,不需停留继续前进(不会park
    • 因为背包空间有限,多次调用unpark仅仅会补充一份备用干粮

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IukUINz9-1657022102207)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20220703152643686.png)]

  • 当前线程调用Unsafe.park()方法
  • 检查_counter,本例情况为0,这时,获得_mutex互斥锁
  • 线程进入_cond条件变量阻塞器
  • 设置_counter=0

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5vq68wW2-1657022102207)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20220703153119795.png)]

  • 调用Unsafe.unpark(Thread_0)方法,设置_counter为1
  • 唤醒_cond条件变量中的Thread_0
  • Thread_0恢复运行
  • 设置_counter为0

3.11 多把锁

3.11.1 多把不相干的锁

有小南小女两个线程,他们想同时执行学习和睡觉的方法,该怎么办呢?

先看看下面的例子:

例1

package com.example.MessageQueue;

import com.example.tools.Sleep;
import lombok.extern.slf4j.Slf4j;

/**
 * @author 我见青山多妩媚
 * @date Create on 2022/7/4 15:55
 */
public class Demo23 {
    public static void main(String[] args) {
        BigRoom bigRoom = new BigRoom();
        new Thread(()->{
           bigRoom.sleep();
        },"小南").start();

        new Thread(()->{
           bigRoom.study();
        },"小女").start();
    }
}

@Slf4j
class BigRoom{
    public void sleep(){
        synchronized(this){
            log.debug("sleeping 2小时");
            Sleep.sleep(2);
        }
    }

    public void study(){
        synchronized (this){
            log.debug("study 1小时");
            Sleep.sleep(1);
        }
    }
}

运行结果:

15:59:42.056 [小南] DEBUG com.example.MessageQueue.BigRoom - sleeping 2小时
15:59:44.074 [小女] DEBUG com.example.MessageQueue.BigRoom - study 1小时

可以看到,是在小南线程睡眠2s后,小女线程才开始运行,这个时候仍然是串行执行,并不是并行执行,并发度并不高

**改进方法:**加入两个小房间,对锁进行细粒度的划分

 	private static final Object studyRoom = new Object();
    private static final Object bedRoom =new Object();

更改后代码如下:

package com.example.MessageQueue;

import com.example.tools.Sleep;
import lombok.extern.slf4j.Slf4j;

/**
 * @author 我见青山多妩媚
 * @date Create on 2022/7/4 15:55
 */
public class Demo23 {
    public static void main(String[] args) {
        BigRoom bigRoom = new BigRoom();
        new Thread(()->{
           bigRoom.sleep();
        },"小南").start();

        new Thread(()->{
           bigRoom.study();
        },"小女").start();
    }
}

@Slf4j
class BigRoom{
    private static final Object studyRoom = new Object();
    private static final Object bedRoom =new Object();


    public void sleep(){
        synchronized(bedRoom){
            log.debug("sleeping 2小时");
            Sleep.sleep(2);
        }
    }

    public void study(){
        synchronized (studyRoom){
            log.debug("study 1小时");
            Sleep.sleep(1);
        }
    }
}

运行结果:

16:03:39.677 [小女] DEBUG com.example.MessageQueue.BigRoom - study 1小时
16:03:39.677 [小南] DEBUG com.example.MessageQueue.BigRoom - sleeping 2小时

**思考:**如果线程请求过多,会不会造成死锁?

3.11.2活跃性
3.11.2.1死锁

有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁

t1线程获得A对象锁,接下来想获取B对象锁

t2线程获取B对象锁,接下来想获取A对象锁

例:

package com.example.MessageQueue;

import com.example.tools.Sleep;
import lombok.extern.slf4j.Slf4j;

/**
 * @author 我见青山多妩媚
 * @date Create on 2022/7/4 16:12
 */
@Slf4j
public class Demo24 {
    public static void main(String[] args) {
          Object A = new Object();
          Object B = new Object();

          Thread t1 = new Thread(()->{
             synchronized (A){
                 log.debug("lock A");
                 Sleep.sleep(1);
                 synchronized (B){
                     log.debug("lock B");
                     log.debug("操作..");
                 }
             }
          },"t1");

          Thread t2 = new Thread(()->{
             synchronized (B){
                 log.debug("lock B");
                 Sleep.sleep(1);
                 synchronized (A){
                     log.debug("lock A");
                     log.debug("操作..");
                 }
             }
          },"t2");

          t1.start();
          t2.start();

    }
}

运行结果:

16:15:25.626 [t2] DEBUG com.example.MessageQueue.Demo24 - lock B
16:15:25.626 [t1] DEBUG com.example.MessageQueue.Demo24 - lock A

发生死锁, log.debug("操作..")始终未打印

3.11.2.2定位死锁

检测死锁可以使用jconsole工具,或者使用jps定位进程id,再用jstack定位死锁

使用jps定位

点击idea工具的Terminal

E:\IDEA举例项目\并发编程>jps
11808 KotlinCompileDaemon
16960 Launcher
17648 Demo24
25072
2684 Jps

可以看到,我们的对象编号是17648

E:\IDEA举例项目\并发编程>jstack 17648
2022-07-04 16:20:24
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.271-b09 mixed mode):

"DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x0000015b4bba8000 nid=0x6ef0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"t2" #13 prio=5 os_prio=0 tid=0x0000015b68bf1800 nid=0x6fac waiting for monitor entry [0x000000361a2ff000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.example.MessageQueue.Demo24.lambda$main$1(Demo24.java:32)
        - waiting to lock <0x000000076be7f0f0> (a java.lang.Object)
        - locked <0x000000076be7f100> (a java.lang.Object)
        at com.example.MessageQueue.Demo24$$Lambda$2/2143192188.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

"t1" #12 prio=5 os_prio=0 tid=0x0000015b68bf0800 nid=0x6f70 waiting for monitor entry [0x000000361a1ff000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.example.MessageQueue.Demo24.lambda$main$0(Demo24.java:21)
        - waiting to lock <0x000000076be7f100> (a java.lang.Object)
        - locked <0x000000076be7f0f0> (a java.lang.Object)
        at com.example.MessageQueue.Demo24$$Lambda$1/110718392.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x0000015b6884b800 nid=0x81c runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x0000015b68820800 nid=0x6f74 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x0000015b6881a800 nid=0x7300 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x0000015b68816800 nid=0x71a0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x0000015b68816000 nid=0x67bc waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x0000015b68814000 nid=0x3428 runnable [0x0000003619afe000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
        at java.net.SocketInputStream.read(SocketInputStream.java:171)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
        at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
        at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
        - locked <0x000000076b854dc8> (a java.io.InputStreamReader)
        at java.io.InputStreamReader.read(InputStreamReader.java:184)
        at java.io.BufferedReader.fill(BufferedReader.java:161)
        at java.io.BufferedReader.readLine(BufferedReader.java:324)
        - locked <0x000000076b854dc8> (a java.io.InputStreamReader)
        at java.io.BufferedReader.readLine(BufferedReader.java:389)
        at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:61)

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x0000015b6683e000 nid=0x662c waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000015b667cd000 nid=0x4dec runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000015b667b2000 nid=0x76d8 in Object.wait() [0x00000036197fe000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076b588ee0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
        - locked <0x000000076b588ee0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000015b667ab000 nid=0x69b4 in Object.wait() [0x00000036196ff000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076b586c00> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x000000076b586c00> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"VM Thread" os_prio=2 tid=0x0000015b66783800 nid=0x7158 runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000015b4bbc0800 nid=0x7094 runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000015b4bbc2000 nid=0x6dc8 runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000015b4bbc3800 nid=0x7084 runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000015b4bbc4800 nid=0x7304 runnable

"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x0000015b4bbc6800 nid=0x7598 runnable

"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000015b4bbc7800 nid=0x243c runnable

"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000015b4bbca800 nid=0x6db8 runnable

"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000015b4bbcb800 nid=0x71cc runnable

"VM Periodic Task Thread" os_prio=2 tid=0x0000015b68850800 nid=0x6aec waiting on condition

JNI global references: 316


Found one Java-level deadlock:
=============================
"t2":
  waiting to lock monitor 0x0000015b667af368 (object 0x000000076be7f0f0, a java.lang.Object),
  which is held by "t1"
"t1":
  waiting to lock monitor 0x0000015b667b1518 (object 0x000000076be7f100, a java.lang.Object),
  which is held by "t2"

Java stack information for the threads listed above:
===================================================
"t2":
        at com.example.MessageQueue.Demo24.lambda$main$1(Demo24.java:32)
        - waiting to lock <0x000000076be7f0f0> (a java.lang.Object)
        - locked <0x000000076be7f100> (a java.lang.Object)
        at com.example.MessageQueue.Demo24$$Lambda$2/2143192188.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
"t1":
        at com.example.MessageQueue.Demo24.lambda$main$0(Demo24.java:21)
        - waiting to lock <0x000000076be7f100> (a java.lang.Object)
        - locked <0x000000076be7f0f0> (a java.lang.Object)
        at com.example.MessageQueue.Demo24$$Lambda$1/110718392.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

更细致:

]

使用jconso

点击JDK/bin目录下的jconsole.exe启动

找到刚刚的进程,点击连接

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H1kfpTz4-1657022102208)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20220704163444395.png)]

点击之后,切换到线程界面

在这里插入图片描述

找到刚刚的线程,t1、t2,点击检测死锁

在这里插入图片描述

t1:

在这里插入图片描述

t2:
在这里插入图片描述

出错行数也都显示了

3.11.3哲学家就餐问题

在这里插入图片描述

有5位哲学家,围坐在圆桌旁

  • 他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭之后接着思考
  • 吃饭的时候要用两根筷子吃,桌子上一共有5跟筷子,每位哲学家左右手边都各有一根筷子
  • 如果筷子被别人使用,自己就需要等待

代码实现:

筷子类

class Chopstick{
    String name;

    public Chopstick(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Chopstick{" +
                "name='" + name + '\'' +
                '}';
    }
}

哲学家类

@Slf4j
class Philosopher extends Thread{
    //左手边的筷子
    final Chopstick left;

    //右手边的筷子
    final Chopstick right;

    public Philosopher(String name,Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }


    @Override
    public void run() {
        while (true){
            // 尝试获取左边筷子
            synchronized (left){
                //尝试获取右边筷子
                synchronized (right){
                    eat();
                }
            }
        }
    }
    private void eat(){
        log.debug("eating");
        Sleep.sleep(1);
    }
}

测试

/**
 * @author 我见青山多妩媚
 * @date Create on 2022/7/4 16:50
 */
public class Demo25 {
    public static void main(String[] args) {
        Chopstick c1 = new Chopstick("1");
        Chopstick c2 = new Chopstick("2");
        Chopstick c3 = new Chopstick("3");
        Chopstick c4 = new Chopstick("4");
        Chopstick c5 = new Chopstick("5");
        new Philosopher("苏格拉底",c1,c2).start();
        new Philosopher("柏拉图",c2,c3).start();
        new Philosopher("亚里士多德",c3,c4).start();
        new Philosopher("赫拉克利特",c4,c5).start();
        new Philosopher("阿基米德",c5,c1).start();
    }
}

运行结果:

16:58:15.200 [亚里士多德] DEBUG com.example.MessageQueue.Philosopher - eating
16:58:15.200 [苏格拉底] DEBUG com.example.MessageQueue.Philosopher - eating
16:58:16.209 [亚里士多德] DEBUG com.example.MessageQueue.Philosopher - eating
16:58:16.209 [阿基米德] DEBUG com.example.MessageQueue.Philosopher - eating
16:58:17.215 [阿基米德] DEBUG com.example.MessageQueue.Philosopher - eating
16:58:18.224 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating

吃了一会后,发生了死锁,哲学家各拿着一根筷子(如果没有死锁,会一直运行下去)

使用jconso查看线程运行状态:

五个线程全部进入死锁
在这里插入图片描述

------------------------------------------------------------------------------------------------------------
名称: 阿基米德
状态: com.example.MessageQueue.Chopstick@38c7b2a3上的BLOCKED, 拥有者: 苏格拉底
总阻止数: 4, 总等待数: 2

堆栈跟踪: 
com.example.MessageQueue.Philosopher.run(Demo25.java:48)
   - 已锁定 com.example.MessageQueue.Chopstick@335704ea
------------------------------------------------------------------------------------------------------------
名称: 苏格拉底
状态: com.example.MessageQueue.Chopstick@6d93340f上的BLOCKED, 拥有者: 柏拉图
总阻止数: 7, 总等待数: 2

堆栈跟踪: 
com.example.MessageQueue.Philosopher.run(Demo25.java:48)
   - 已锁定 com.example.MessageQueue.Chopstick@38c7b2a3
------------------------------------------------------------------------------------------------------------
名称: 柏拉图
状态: com.example.MessageQueue.Chopstick@951fff6上的BLOCKED, 拥有者: 亚里士多德
总阻止数: 2, 总等待数: 0

堆栈跟踪: 
com.example.MessageQueue.Philosopher.run(Demo25.java:48)
   - 已锁定 com.example.MessageQueue.Chopstick@6d93340f
------------------------------------------------------------------------------------------------------------
名称: 亚里士多德
状态: com.example.MessageQueue.Chopstick@5fc07890上的BLOCKED, 拥有者: 赫拉克利特
总阻止数: 10, 总等待数: 2

堆栈跟踪: 
com.example.MessageQueue.Philosopher.run(Demo25.java:48)
   - 已锁定 com.example.MessageQueue.Chopstick@951fff6
------------------------------------------------------------------------------------------------------------
名称: 赫拉克利特
状态: com.example.MessageQueue.Chopstick@335704ea上的BLOCKED, 拥有者: 阿基米德
总阻止数: 3, 总等待数: 1

堆栈跟踪: 
com.example.MessageQueue.Philosopher.run(Demo25.java:48)
   - 已锁定 com.example.MessageQueue.Chopstick@5fc07890
------------------------------------------------------------------------------------------------------------

一人拿一根筷子都不放下,等待对方放下筷子,造成死锁

解决方法:使用锁超时解决哲学家就餐问题

3.11.4活锁

活锁出现在两个线程互相改变对方的结束条件,导致谁也无法结束

package com.example.MessageQueue;

import com.example.tools.Sleep;
import lombok.extern.slf4j.Slf4j;

/**
 * @author 我见青山多妩媚
 * @date Create on 2022/7/4 17:10
 */
@Slf4j
public class Demo26 {
    /**
     * Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量
     * 是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
     */
    static volatile int count = 10;
    static final Object lock = new Object();

    public static void main(String[] args) {
        new Thread(()->{
           while (count > 0){
               Sleep.sleep(0.2);
               count--;
               log.debug("count{}",count);
           }
        },"t1").start();

        new Thread(()->{
           while (count < 20){
               Sleep.sleep(0.2);
               count++;
               log.debug("count{}",count);
           }
        },"t2").start();
    }
}

运行结果:

17:16:01.283 [t2] DEBUG com.example.MessageQueue.Demo26 - count9
17:16:01.283 [t1] DEBUG com.example.MessageQueue.Demo26 - count9
17:16:01.500 [t1] DEBUG com.example.MessageQueue.Demo26 - count10
17:16:01.500 [t2] DEBUG com.example.MessageQueue.Demo26 - count10
17:16:01.705 [t2] DEBUG com.example.MessageQueue.Demo26 - count10
17:16:01.705 [t1] DEBUG com.example.MessageQueue.Demo26 - count9
17:16:01.908 [t1] DEBUG com.example.MessageQueue.Demo26 - count9
17:16:01.908 [t2] DEBUG com.example.MessageQueue.Demo26 - count10
17:16:02.112 [t2] DEBUG com.example.MessageQueue.Demo26 - count9
17:16:02.112 [t1] DEBUG com.example.MessageQueue.Demo26 - count9
17:16:02.315 [t1] DEBUG com.example.MessageQueue.Demo26 - count9
17:16:02.315 [t2] DEBUG com.example.MessageQueue.Demo26 - count9
17:16:02.521 [t1] DEBUG com.example.MessageQueue.Demo26 - count8
17:16:02.521 [t2] DEBUG com.example.MessageQueue.Demo26 - count8
17:16:02.725 [t1] DEBUG com.example.MessageQueue.Demo26 - count7
17:16:02.725 [t2] DEBUG com.example.MessageQueue.Demo26 - count7
17:16:02.928 [t2] DEBUG com.example.MessageQueue.Demo26 - count7
17:16:02.928 [t1] DEBUG com.example.MessageQueue.Demo26 - count6
17:16:03.133 [t1] DEBUG com.example.MessageQueue.Demo26 - count6
17:16:03.133 [t2] DEBUG com.example.MessageQueue.Demo26 - count6
17:16:03.337 [t2] DEBUG com.example.MessageQueue.Demo26 - count5
17:16:03.337 [t1] DEBUG com.example.MessageQueue.Demo26 - count5
17:16:03.542 [t1] DEBUG com.example.MessageQueue.Demo26 - count4
17:16:03.542 [t2] DEBUG com.example.MessageQueue.Demo26 - count4
17:16:03.748 [t1] DEBUG com.example.MessageQueue.Demo26 - count5
17:16:03.748 [t2] DEBUG com.example.MessageQueue.Demo26 - count5
17:16:03.951 [t1] DEBUG com.example.MessageQueue.Demo26 - count4
17:16:03.951 [t2] DEBUG com.example.MessageQueue.Demo26 - count5
17:16:04.153 [t2] DEBUG com.example.MessageQueue.Demo26 - count6
17:16:04.153 [t1] DEBUG com.example.MessageQueue.Demo26 - count6
17:16:04.358 [t2] DEBUG com.example.MessageQueue.Demo26 - count6
17:16:04.358 [t1] DEBUG com.example.MessageQueue.Demo26 - count6
17:16:04.559 [t1] DEBUG com.example.MessageQueue.Demo26 - count6
17:16:04.559 [t2] DEBUG com.example.MessageQueue.Demo26 - count7

互相改变对方的停止条件,都不会终止,造成活锁

解决办法:

让睡眠时间不同,或者增加随机的睡眠时间,让两个线程交错开,避免活锁的产生

3.11.5饥饿

很多教程把饥饿定义为,一个线程由于优先级太低,始终得不到cpu调度执行,也不能够得到结果,饥饿的情况不易演示,到读写锁时会涉及饥饿问题

一个线程饥饿的例子,先来看看使用顺序加锁的方式解决之前的死锁问题

线程A先获取对象A,然后获取对象B

线程B先获取对象B,然后获取对象A(详见3.11.2.1死锁

在这里插入图片描述

顺序加锁的解决方案

在这里插入图片描述

演示:

3.11.3哲学家就餐问题,将

new Philosopher("阿基米德",c5,c1).start();
//改为
new Philosopher("阿基米德",c1,c5).start();

再次运行代码,结果为

17:30:29.248 [亚里士多德] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:29.248 [苏格拉底] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:30.257 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:31.271 [亚里士多德] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:31.271 [阿基米德] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:32.273 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:33.280 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:34.286 [亚里士多德] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:35.300 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:36.301 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:37.302 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:38.317 [亚里士多德] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:39.322 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:40.322 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:41.329 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:42.332 [亚里士多德] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:43.332 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:44.335 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:45.343 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:46.349 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:47.351 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:48.353 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:49.360 [亚里士多德] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:50.375 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:50.375 [柏拉图] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:51.389 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:52.397 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:53.408 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:54.413 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:55.418 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:56.432 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:57.446 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:58.462 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:30:59.465 [亚里士多德] DEBUG com.example.MessageQueue.Philosopher - eating
17:31:00.472 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating
17:31:01.480 [赫拉克利特] DEBUG com.example.MessageQueue.Philosopher - eating

使用工具查看是否死锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P4WpWD8m-1657022102213)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20220704173124055.png)]

可以看到并没有死锁

但是对于运行结果,可以看到,基本都是[赫拉克利特]线程在吃法,其他线程很少,这就是饥饿问题

3.12 ReentrantLock

ReentrantLock主要用到unsafe的CAS(原子性)和park(阻塞)两个功能实现锁(CAS + park )

相对于synchronized它具备以下几点

  • 可中断
  • 可以设置超时时间
  • 可以设置为公平锁
  • 支持多个条件变量

与synchronized一样,都可支持可重入

基本语法:

//获取锁
reentrantLock.lock();
try{
    //需要上锁的代码
}finally{
    //释放锁
    reentrantLock.unlock();
}

与synchronized()不同的是,synchronized(obj) 可以指定锁住的对象,但是ReentrantLock仅仅只能锁住从lock() -> unlock()(这样说不是很严谨,因为上锁的方法不止一个,总之就是从上锁到解锁的那段代码都是被锁上的)内的代码,并且要保证ReentrantLock是线程间共享。

3.12.1可重入

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获得这把锁,如果是不可重入的,那么第二次获得锁时,自己也会被锁挡住

package com.example.MessageQueue;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author 我见青山多妩媚
 * @date Create on 2022/7/4 19:20
 */
@Slf4j
public class Demo27 {
    static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        lock.lock();
        try {
            log.debug("进入主方法");
            m1();
        } finally {
            lock.unlock();
        }
    }
    //模拟锁重入
    public static void m1(){
        lock.lock();
        try {
            log.debug("进入m1");
            m2();
        } finally {
            lock.unlock();
        }
    }

    public static void m2(){
        lock.lock();
        try {
            log.debug("进入m2");
        } finally {
            lock.unlock();
        }
    }

}

运行结果:

19:25:16.549 [main] DEBUG com.example.MessageQueue.Demo27 - 进入主方法
19:25:16.552 [main] DEBUG com.example.MessageQueue.Demo27 - 进入m1
19:25:16.552 [main] DEBUG com.example.MessageQueue.Demo27 - 进入m2
3.12.2可打断
package com.example.MessageQueue;

import com.example.tools.Sleep;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author 我见青山多妩媚
 * @date Create on 2022/7/4 19:28
 */
@Slf4j
public class Demo28 {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            try {
                //如果没有竞争,此方法会获取lock对象的锁
                //如果有竞争,进入阻塞队列,可以被其他线程使用interrupt方法打断
                log.debug("尝试获取到锁");
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("没有获取到锁");
                return;
            }

            try {
                log.debug("获取到锁");
            } finally {
                lock.unlock();
            }

        },"t1");

        lock.lock();
        t1.start();

        Sleep.sleep(1);
        log.debug("打断t1");
        t1.interrupt();

    }
}

运行结果:

19:46:14.254 [t1] DEBUG com.example.MessageQueue.Demo28 - 尝试获取到锁
19:46:15.255 [main] DEBUG com.example.MessageQueue.Demo28 - 打断t1
19:46:15.255 [t1] DEBUG com.example.MessageQueue.Demo28 - 没有获取到锁
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at com.example.MessageQueue.Demo28.lambda$main$0(Demo28.java:22)
	at java.lang.Thread.run(Thread.java:748)

使用打断操作,是为了防止死锁的发生,如果使用lock.loc(),会一直等待,不会抛异常信息

3.12.3锁超时

可以让锁立刻失败

package com.example.MessageQueue;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author 我见青山多妩媚
 * @date Create on 2022/7/5 16:36
 */
@Slf4j
public class Demo29 {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            //尝试获得锁
            log.debug("尝试获得锁");
           if(!lock.tryLock()){
               log.debug("获取不到锁");
               return;
           }
            //给定等待时间获取锁
//            try {
//                if(!lock.tryLock(2,TimeUnit.SECONDS)){
//                    log.debug("获取不到锁");
//                    return;
//                }
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//                return;
//            }

           try {
               //获取到锁
               log.debug("获取到锁");
           }finally {
               lock.unlock();
           }

        },"t1");

        log.debug("主线程获取到锁");
        lock.lock();
       
        t1.start();
        //等待时间时让主线程释放锁
//        Sleep.sleep(1);
//        log.debug("主线程释放了锁");
//        lock.unlock();
    }
}

运行结果:

16:42:12.654 [main] DEBUG com.example.MessageQueue.Demo29 - 主线程获取到锁
16:42:12.656 [t1] DEBUG com.example.MessageQueue.Demo29 - 尝试获得锁
16:42:12.657 [t1] DEBUG com.example.MessageQueue.Demo29 - 获取不到锁

//等待时间后的运行结果
16:49:01.580 [main] DEBUG com.example.MessageQueue.Demo29 - 主线程获取到锁
16:49:01.584 [t1] DEBUG com.example.MessageQueue.Demo29 - 尝试获得锁
16:49:02.597 [main] DEBUG com.example.MessageQueue.Demo29 - 主线程释放了锁
16:49:02.597 [t1] DEBUG com.example.MessageQueue.Demo29 - 获取到锁

用锁超时解决哲学家就餐问题

3.11.3哲学家就餐问题

代码实现:

筷子类

//筷子类继承ReentrantLock
class Chopsticks extends ReentrantLock {
    String name;

    public Chopsticks(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Chopstick{" +
                "name='" + name + '\'' +
                '}';
    }
}

哲学家类

@Slf4j
class Philosophers extends Thread{
    //左手边的筷子
    final Chopsticks left;

    //右手边的筷子
    final Chopsticks right;

    public Philosophers(String name,Chopsticks left, Chopsticks right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    @Override
    public void run() {
        while (true) {
            //与synchronized ()不同的是,如果获取锁失败(无论左手筷子还是右手筷子),会把当前的锁对象释放掉,不会一直等下去
            // 尝试获取左边筷子
            if(left.tryLock()){
                try {
                    //尝试获取右边筷子
                    if(right.tryLock()){
                        try {
                            //左手筷子,有手筷子都拿到之后,就可以吃饭了
                            eat();
                        }finally {
                            right.unlock();
                        }
                    }
                }finally {
                    //确保锁可以释放掉
                    left.unlock();
                }
            }
        }
    }
    private void eat(){
        log.debug("eating");
        Sleep.sleep(0.2);
    }
}

测试

/**
 * @author 我见青山多妩媚
 * @date Create on 2022/7/5 16:55
 */
public class Demo25_Solve {
    public static void main(String[] args) {
       	Chopsticks c1 = new Chopsticks("1");
        Chopsticks c2 = new Chopsticks("2");
        Chopsticks c3 = new Chopsticks("3");
        Chopsticks c4 = new Chopsticks("4");
        Chopsticks c5 = new Chopsticks("5");
        new Philosophers("苏格拉底",c1,c2).start();
        new Philosophers("柏拉图",c2,c3).start();
        new Philosophers("亚里士多德",c3,c4).start();
        new Philosophers("赫拉克利特",c4,c5).start();
        new Philosophers("阿基米德",c5,c1).start();
    }
}

运行结果:

17:07:38.884 [苏格拉底] DEBUG com.example.MessageQueue.Philosophers - eating
17:07:38.884 [亚里士多德] DEBUG com.example.MessageQueue.Philosophers - eating
17:07:39.090 [阿基米德] DEBUG com.example.MessageQueue.Philosophers - eating
17:07:39.090 [柏拉图] DEBUG com.example.MessageQueue.Philosophers - eating
17:07:39.300 [阿基米德] DEBUG com.example.MessageQueue.Philosophers - eating
17:07:39.300 [柏拉图] DEBUG com.example.MessageQueue.Philosophers - eating
17:07:39.510 [亚里士多德] DEBUG com.example.MessageQueue.Philosophers - eating
17:07:39.512 [苏格拉底] DEBUG com.example.MessageQueue.Philosophers - eating
17:07:39.723 [苏格拉底] DEBUG com.example.MessageQueue.Philosophers - eating
17:07:39.725 [亚里士多德] DEBUG com.example.MessageQueue.Philosophers - eating
17:07:39.943 [亚里士多德] DEBUG com.example.MessageQueue.Philosophers - eating
17:07:39.943 [阿基米德] DEBUG com.example.MessageQueue.Philosophers - eating
17:07:40.151 [阿基米德] DEBUG com.example.MessageQueue.Philosophers - eating
17:07:40.159 [亚里士多德] DEBUG com.example.MessageQueue.Philosophers - eating
17:07:40.365 [亚里士多德] DEBUG com.example.MessageQueue.Philosophers - eating
17:07:40.370 [阿基米德] DEBUG com.example.MessageQueue.Philosophers - eating
17:07:40.578 [阿基米德] DEBUG com.example.MessageQueue.Philosophers - eating
17:07:40.578 [亚里士多德] DEBUG com.example.MessageQueue.Philosophers - eating
17:07:40.789 [亚里士多德] DEBUG com.example.MessageQueue.Philosophers - eating
17:07:40.789 [阿基米德] DEBUG com.example.MessageQueue.Philosophers - eating
17:07:40.999 [阿基米德] DEBUG com.example.MessageQueue.Philosophers - eating
17:07:41.000 [柏拉图] DEBUG com.example.MessageQueue.Philosophers - eating
17:07:41.210 [阿基米德] DEBUG com.example.MessageQueue.Philosophers - eating

可以看到,大家吃的都很快乐,没有产生死锁问题

3.12.5公平锁

ReentrantLock默认是不公平的

	/**
	 *创建一个 {@code ReentrantLock} 的实例。这相当于使用 {@code ReentrantLock(false)}。
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

	/**
	 * 使用给定的公平策略创建 {@code ReentrantLock} 的实例。 
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} 如果这个锁应该使用公平的排序策略
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

测试不好实现,以后有例子

3.12.6条件变量

synchronized中也有条件变量,就是我们讲原理时哪个waitSet休息室,当条件不满足时进入waitSet等待

ReentrantLock的条件变量比synchronized强大之处在于,他是支持多个条件变量的,这就好比

  • synchronized是那些不满足条件的线程都在一间休息室等消息
  • 而ReentrantLock支持多间休息室,有专门等烟的休息室,专门等早餐的休息室,唤醒时也是按休息室来唤醒

使用流程

  • await 前需要获得锁
  • await执行后,会释放锁,进入conditionObject等待
  • await的线程被唤醒(或打断、超时等)重新竞争lock锁
  • 竞争lock锁成功后,await后继续执行
package com.example.MessageQueue;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author 我见青山多妩媚
 * @date Create on 2022/7/5 17:30
 */
public class test {
    static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        //创建一个新的条件变量(休息室),同一把锁可以有多个锁对象
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();

        lock.lock();

        //进入休息室等待
        try {
            //释放当前锁持有的锁
            condition1.await();

            //叫醒condition1等待的某一个线程
            condition1.signal();

            //叫醒所有等待的线程
            condition1.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

例子

小南小女送干活

  • 小南需要烟才能干活
  • 小女需要外卖才能干活
package com.example.MessageQueue;

import com.example.tools.Sleep;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author 我见青山多妩媚
 * @date Create on 2022/7/5 17:30
 */
@Slf4j
public class Demo30 {
    static ReentrantLock room = new ReentrantLock();
    //等烟的休息室
    static Condition waitCigaretteSet = room.newCondition();
    //等外面的休息室
    static Condition waitTakeoutSet = room.newCondition();
    //有烟没
    static boolean hasCigarette = false;
    //有外卖没
    static boolean hasTakeout = false;

    public static void main(String[] args) {
        new Thread(()->{
            room.lock();
            try{
                log.debug("有烟没{}",hasCigarette);
                while (!hasCigarette){
                    log.debug("没烟,先歇会");
                    try {
                        //进入等烟的休息室
                        waitCigaretteSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没{}",hasCigarette);
                log.debug("可以开始干活了");
            }finally {
                //释放锁
                room.unlock();
            }
        },"小南").start();

        new Thread(()->{
            room.lock();
            try{
                log.debug("外卖到了没{}",hasTakeout);
                while (!hasTakeout){
                    log.debug("没到,先歇会");
                    try {
                        //进入等外卖的休息室
                        waitTakeoutSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("外卖到了没{}",hasTakeout);
                log.debug("可以开始干活了");
            }finally {
                //释放锁
                room.unlock();
            }
        },"小女").start();

        Sleep.sleep(1);
        new Thread(()->{
            room.lock();
            try{
                log.debug("外卖送到了");
                //唤醒等外卖室
                waitTakeoutSet.signal();
                hasTakeout = true;
            }finally {
                room.unlock();
            }
        },"送外卖的").start();

        Sleep.sleep(1);
        new Thread(()->{
            room.lock();
            try{
                log.debug("烟送到了没");
                //唤醒等烟室
                waitCigaretteSet.signal();
                hasCigarette = true;
            }finally {
                room.unlock();
            }
        },"送烟的").start();

    }
}

运行结果

17:54:58.326 [小南] DEBUG com.example.MessageQueue.Demo30 - 有烟没false
17:54:58.330 [小南] DEBUG com.example.MessageQueue.Demo30 - 没烟,先歇会
17:54:58.330 [小女] DEBUG com.example.MessageQueue.Demo30 - 外卖到了没false
17:54:58.330 [小女] DEBUG com.example.MessageQueue.Demo30 - 没到,先歇会
17:54:59.330 [送外卖的] DEBUG com.example.MessageQueue.Demo30 - 外卖送到了
17:54:59.330 [小女] DEBUG com.example.MessageQueue.Demo30 - 外卖到了没true
17:54:59.330 [小女] DEBUG com.example.MessageQueue.Demo30 - 可以开始干活了
17:55:00.340 [送烟的] DEBUG com.example.MessageQueue.Demo30 - 烟送到了没
17:55:00.340 [小南] DEBUG com.example.MessageQueue.Demo30 - 有烟没true
17:55:00.340 [小南] DEBUG com.example.MessageQueue.Demo30 - 可以开始干活了
3.13小结

需要掌握的是:

基础方面:

  • 分析多线程资源时,哪些代码属于临界区
  • 使用synchronized互斥解决临界区的线程安全问题
    • 掌握synchronized锁对象语法
    • 掌握synchronized加载成员方法和静态方法语法
    • 掌握wait\notify同步方法
  • 使用lock互斥锁解决临界区的线程安全问题
    • 掌握lock的使用细节:可打断、锁超时、公平锁、条件变量
  • 学会分析变量的线程安全性、掌握常见线程安全类的使用
  • 了解线程活跃性问题:死锁、活锁、饥饿

应用方面:

  • 互斥:使用synchronized或Lock达到共享资源互斥效果
  • 同步:使用wait\notify或Lock的条件变量来达到线程间通信效果

原理方面

  • monitor(管程)、synchronized、wait\notify原理
  • synchronized进阶原理
  • park & unpark原理

模式方面:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值