Park & Unpark、线程状态转换、死锁、活锁、饥饿

1.Park & Unpark

1.1 介绍

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

先park()再unpark()

/**
 * @ClassName TestParkUnpark
 * @author: shouanzh
 * @Description TestParkUnpark
 * @date 2022/3/10 20:38
 */
@Slf4j
public class TestParkUnpark {

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(() ->{
            log.debug("start");

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            log.debug("park...");
            LockSupport.park(); // WAIT状态

            log.debug("end...");
        },"t1");

        thread.start();

        Thread.sleep(2000);

        log.debug("unpark...");
        LockSupport.unpark(thread);

    }

}

2022-03-10 20:43:07 [t1] - start
2022-03-10 20:43:08 [t1] - park...
2022-03-10 20:43:09 [main] - unpark...
2022-03-10 20:43:09 [t1] - end...

Process finished with exit code 0

先unpark()再park()

/**
 * @ClassName TestParkUnpark
 * @author: shouanzh
 * @Description TestParkUnpark
 * @date 2022/3/10 20:38
 */
@Slf4j
public class TestParkUnpark {

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(() ->{
            log.debug("start");

            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            log.debug("park...");
            LockSupport.park();

            log.debug("end...");
        },"t1");

        thread.start();

        Thread.sleep(1000);

        log.debug("unpark...");
        LockSupport.unpark(thread);

    }

}

2022-03-10 20:46:39 [t1] - start
2022-03-10 20:46:40 [main] - unpark...
2022-03-10 20:46:41 [t1] - park...
2022-03-10 20:46:41 [t1] - end...

Process finished with exit code 0

1.2 与 Object 的 wait & notify相比

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

1.3 Park & Unpark 原理

每个线程都有自己的一个 Parker 对象,由三部分组成 _counter, _cond和 _mutex

线程就像一个旅人, Parker 就像他随身携带的背包, _cond条件变量就好比背包中的帐篷。 _counter 就好比背包 中的备用干粮(0为耗尽,1为充足)

  • 调用 park 就是要看需不需要停下来歇息

    • 如果备用干粮耗尽,那么钻进帐篷歇息
    • 如果备用干粮充足,那么不需停留,继续前进
  • 调用 unpark,就好比令干粮充足

    • 如果这时线程还在帐篷,就唤醒让他继续前进
    • 如果这时线程还在运行,那么下次他调用 park 时,仅是消耗掉备用干粮,不需停留继续前进
    • 因为背包空间有限,多次调用 unpark 仅会补充一份备用干粮

先调用park()再调用unpark()

调用park()

1.当前线程调用 Unsafe.park() 方法
2.检查 _counter, 本情况为0, 这时, 获得_mutex 互斥锁(mutex对象有个等待队列 _cond)
3.线程进入 _cond 条件变量阻塞
4.设置_counter = 0 (没干粮了)
在这里插入图片描述
再调用unpark()

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

在这里插入图片描述

先调用unpark再调用park的过程
1.调用 Unsafe.unpark(Thread_0)方法,设置 _counter 为 1
2.当前线程调用 Unsafe.park() 方法
3.检查 _counter,本情况为 1,这时线程 无需阻塞,继续运行
4.设置 _counter 为 0

在这里插入图片描述

2.线程状态转换

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.多把锁

3.1 多把不相干的锁

  • 一间大屋子有两个功能:睡觉、学习,互不相干。
  • 现在小南要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低
  • 小南获得锁之后, 学完习之后, 小女才能进来睡觉。
  • 解决方法是准备多个房间(多个对象锁)
@Slf4j(topic = "z.BigRoomTest")
public class BigRoomTest {
    public static void main(String[] args) {

        BigRoom bigRoom = new BigRoom();

        new Thread(() -> {
            try {
                bigRoom.sleep();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "小南").start();

        new Thread(() -> {
            try {
                bigRoom.study();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "小女").start();
    }
}

@Slf4j(topic = "z.BigRoom")
class BigRoom {
    public void sleep() throws InterruptedException {
        synchronized (this) {
            log.debug("sleeping 2 小时");
            Thread.sleep(2000);
        }
    }

    public void study() throws InterruptedException {
        synchronized (this) {
            log.debug("study 1 小时");
            Thread.sleep(1000);
        }
    }
}

相当于串行执行, 因为锁对象是整个屋子, 所以并发性很低

2022-03-10 22:20:05 [小南] - sleeping 2 小时
2022-03-10 22:20:07 [小女] - study 1 小时

Process finished with exit code 0

解决方法是准备多个房间(多个对象锁)

@Slf4j(topic = "z.BigRoomTest")
public class BigRoomTest {
    public static void main(String[] args) {

        BigRoom bigRoom = new BigRoom();

        new Thread(() -> {
            try {
                bigRoom.sleep();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "小南").start();

        new Thread(() -> {
            try {
                bigRoom.study();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "小女").start();
    }
}

@Slf4j(topic = "z.BigRoom")
class BigRoom {

    private final Object sleepRoom = new Object();
    private final Object studyRoom = new Object();


    public void sleep() throws InterruptedException {
        synchronized (sleepRoom) {
            log.debug("sleeping 2 小时");
            Thread.sleep(2000);
        }
    }

    public void study() throws InterruptedException {
        synchronized (studyRoom) {
            log.debug("study 1 小时");
            Thread.sleep(1000);
        }
    }
}

2022-03-10 22:26:34 [小南] - sleeping 2 小时
2022-03-10 22:26:34 [小女] - study 1 小时

Process finished with exit code 0

将锁的粒度细分

  • 好处,是可以增强并发度
  • 坏处,如果一个线程需要同时获得多把锁,就容易发生死锁

3.2 死锁

线程1获取A对象锁, 线程2获取B对象锁; 此时线程1又想获取B对象锁, 线程2又想获取A对象锁; 它们都等着对象释放锁, 此时就称为死锁

/**
 * @ClassName TestDeadLock
 * @author: shouanzh
 * @Description jps -l  jstack 进程id
 * @date 2022/3/10 22:37
 */
public class TestDeadLock {

    public static void main(String[] args) {
        final Object A = new Object();
        final Object B = new Object();

        new Thread(()->{
            synchronized (A) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (B) {

                }
            }
        }).start();

        new Thread(()->{
            synchronized (B) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (A) {

                }
            }
        }).start();
    }

}

3.3 定位死锁

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

~ jps
26417
26919 Jps
26459 RemoteMavenServer36
26828 Launcher
26829 TestDeadLock
➜  ~~ jstack 26829
2022-03-10 22:43:21
Full thread dump OpenJDK 64-Bit Server VM (25.312-b07 mixed mode):

"Thread-1" #12 prio=5 os_prio=31 tid=0x0000000136059000 nid=0x5903 waiting for monitor entry [0x0000000170622000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at com.concurrent.parktest.TestDeadLock.lambda$main$1(TestDeadLock.java:37)
	- waiting to lock <0x000000076ac3e828> (a java.lang.Object)
	- locked <0x000000076ac3e838> (a java.lang.Object)
	at com.concurrent.parktest.TestDeadLock$$Lambda$2/1149319664.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)

"Thread-0" #11 prio=5 os_prio=31 tid=0x0000000136015000 nid=0x5803 waiting for monitor entry [0x0000000170416000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at com.concurrent.parktest.TestDeadLock.lambda$main$0(TestDeadLock.java:24)
	- waiting to lock <0x000000076ac3e838> (a java.lang.Object)
	- locked <0x000000076ac3e828> (a java.lang.Object)
	at com.concurrent.parktest.TestDeadLock$$Lambda$1/1023892928.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)

// 找到一个死锁
Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x000000013505e820 (object 0x000000076ac3e828, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00000001350610b0 (object 0x000000076ac3e838, a java.lang.Object),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
	at com.concurrent.parktest.TestDeadLock.lambda$main$1(TestDeadLock.java:37)
	- waiting to lock <0x000000076ac3e828> (a java.lang.Object)
	- locked <0x000000076ac3e838> (a java.lang.Object)
	at com.concurrent.parktest.TestDeadLock$$Lambda$2/1149319664.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)
"Thread-0":
	at com.concurrent.parktest.TestDeadLock.lambda$main$0(TestDeadLock.java:24)
	- waiting to lock <0x000000076ac3e838> (a java.lang.Object)
	- locked <0x000000076ac3e828> (a java.lang.Object)
	at com.concurrent.parktest.TestDeadLock$$Lambda$1/1023892928.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

3.4 死锁举例 - 哲学家就餐问题

在这里插入图片描述
有五位哲学家,围坐在圆桌旁。

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

当每个哲学家即线程持有一根筷子时,他们都在等待另一个线程释放锁,因此造成了死锁。

/**
 * Description: 使用synchronized加锁, 导致哲学家就餐问题,
 * 死锁: 核心原因是因为synchronized的锁是不可打断的, 进入阻塞队列
 * 需要一直等待别的线程释放锁
 *
 */
@Slf4j(topic = "z.PhilosopherEat")
public class PhilosopherEat {
    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();

    }
}

// 哲学家
@Slf4j(topic = "z.Philosopher")
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;
    }

    @SneakyThrows
    @Override
    public void run() {
        while (true) {
            // 尝试获取左手筷子
            synchronized (left) {
                // 尝试获取右手筷子
                synchronized (right) {
                    eat();
                }
            }
        }
    }

    // 吃饭
    private void eat() throws InterruptedException {
        log.debug("eating...");
        Thread.sleep(500);
    }
}


// 筷子
class Chopstick{
    String name;

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

    @Override
    public String toString() {
        return "筷子{" + name + '}';
    }
}

在这里插入图片描述
检测死锁

~ jps
26417
26994 Launcher
26995 PhilosopherEat
26459 RemoteMavenServer36
27037 Jps
➜  ~~~ jstack 26995

Found one Java-level deadlock:
=============================
"阿基米德":
  waiting to lock monitor 0x000000014f8c3760 (object 0x000000076bae99a8, a com.concurrent.parktest.Chopstick),
  which is held by "苏格拉底"
"苏格拉底":
  waiting to lock monitor 0x000000012f0fa210 (object 0x000000076bae99b8, a com.concurrent.parktest.Chopstick),
  which is held by "柏拉图"
"柏拉图":
  waiting to lock monitor 0x000000012f0fa160 (object 0x000000076bae99c8, a com.concurrent.parktest.Chopstick),
  which is held by "亚里士多德"
"亚里士多德":
  waiting to lock monitor 0x000000012f0fce10 (object 0x000000076bae99d8, a com.concurrent.parktest.Chopstick),
  which is held by "赫拉克利特"
"赫拉克利特":
  waiting to lock monitor 0x000000012f0fcd60 (object 0x000000076bae99e8, a com.concurrent.parktest.Chopstick),
  which is held by "阿基米德"

Java stack information for the threads listed above:
===================================================
"阿基米德":
	at com.concurrent.parktest.Philosopher.run(PhilosopherEat.java:50)
	- waiting to lock <0x000000076bae99a8> (a com.concurrent.parktest.Chopstick)
	- locked <0x000000076bae99e8> (a com.concurrent.parktest.Chopstick)
"苏格拉底":
	at com.concurrent.parktest.Philosopher.run(PhilosopherEat.java:50)
	- waiting to lock <0x000000076bae99b8> (a com.concurrent.parktest.Chopstick)
	- locked <0x000000076bae99a8> (a com.concurrent.parktest.Chopstick)
"柏拉图":
	at com.concurrent.parktest.Philosopher.run(PhilosopherEat.java:50)
	- waiting to lock <0x000000076bae99c8> (a com.concurrent.parktest.Chopstick)
	- locked <0x000000076bae99b8> (a com.concurrent.parktest.Chopstick)
"亚里士多德":
	at com.concurrent.parktest.Philosopher.run(PhilosopherEat.java:50)
	- waiting to lock <0x000000076bae99d8> (a com.concurrent.parktest.Chopstick)
	- locked <0x000000076bae99c8> (a com.concurrent.parktest.Chopstick)
"赫拉克利特":
	at com.concurrent.parktest.Philosopher.run(PhilosopherEat.java:50)
	- waiting to lock <0x000000076bae99e8> (a com.concurrent.parktest.Chopstick)
	- locked <0x000000076bae99d8> (a com.concurrent.parktest.Chopstick)

Found 1 deadlock.

3.5 活锁

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

/**
 * @ClassName TestLiveLock
 * @author: shouanzh
 * @Description 活锁出现在两个线程 互相改变对方的结束条件,谁也无法结束。
 * @date 2022/3/10 23:13
 */
@Slf4j
public class TestLiveLock {

    static volatile int count = 10;

    static final Object OBJECT = new Object();


    public static void main(String[] args) {

        new Thread(() -> {
            while (count > 0) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                log.debug("count:{}", count);
            }
        }, "t1").start();


        new Thread(() -> {
            while (count < 20) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count++;
                log.debug("count:{}", count);
            }
        }, "t2").start();

    }

}

避免活锁的方法

  • 在线程执行时,中途给予 不同的间隔时间, 让某个线程先结束即可。

死锁与活锁的区别

  • 死锁是因为线程互相持有对象想要的锁,并且都不释放,最后到时线程阻塞,停止运行的现象。
  • 活锁是因为线程间修改了对方的结束条件,而导致代码一直在运行,却一直运行不完的现象。

3.6 饥饿

一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束

3.7 避免死锁的方法

死锁
在这里插入图片描述

顺序加锁
在这里插入图片描述

@Slf4j(topic = "z.PhilosopherEat")
public class PhilosopherEat {
    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("阿基米德", c1, c5).start(); // 饥饿

    }
}

// 哲学家
@Slf4j(topic = "z.Philosopher")
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;
    }

    @SneakyThrows
    @Override
    public void run() {
        while (true) {
            // 尝试获取左手筷子
            synchronized (left) {
                // 尝试获取右手筷子
                synchronized (right) {
                    eat();
                }
            }
        }
    }

    // 吃饭
    private void eat() throws InterruptedException {
        log.debug("eating...");
        Thread.sleep(500);
    }
}


// 筷子
class Chopstick{
    String name;

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

    @Override
    public String toString() {
        return "筷子{" + name + '}';
    }
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中的死锁活锁是多线程并发编程中的常见问题,这些问题可能会导致线程无法向前执行或陷入死循环。下面分别介绍Java死锁活锁的概念和案例。 1. 死锁 死锁是指两个或多个线程相互等待对方释放锁,从而导致所有线程都无法向前执行的状态。简单来说,当两个或多个线程都占有某些资源并且又想要获取对方占有的资源时,就会发生死锁。 下面是一个Java中的死锁例子: ```java public class DeadlockExample { private Object lock1 = new Object(); private Object lock2 = new Object(); public void method1() { synchronized (lock1) { System.out.println("Method 1: Holding lock 1..."); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock2) { System.out.println("Method 1: Holding lock 1 and lock 2..."); } } } public void method2() { synchronized (lock2) { System.out.println("Method 2: Holding lock 2..."); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock1) { System.out.println("Method 2: Holding lock 1 and lock 2..."); } } } public static void main(String[] args) { DeadlockExample example = new DeadlockExample(); Thread t1 = new Thread(() -> example.method1()); Thread t2 = new Thread(() -> example.method2()); t1.start(); t2.start(); } } ``` 在这个例子中,两个线程t1和t2分别调用了method1和method2方法,这两个方法都需要获取lock1和lock2两个锁才能继续执行。由于t1占用了lock1并等待lock2,而t2占用了lock2并等待lock1,因此两个线程都无法释放自己占用的锁,从而陷入死锁状态。 2. 活锁 活锁是指线程们都在运行并尝试执行任务,但是由于某些条件始终无法满足,导致线程们一直在重试,但是最终无法完成任务。这种情况下,线程们看起来像是在不断地活动,但是实际上却没有任何进展。 下面是一个Java中的活锁例子: ```java public class LiveLockExample { private boolean isPolite; public LiveLockExample(boolean isPolite) { this.isPolite = isPolite; } public void bow(LiveLockExample partner) { while (true) { if (isPolite) { System.out.println("I'm polite, I'll bow first"); partner.bowBack(this); isPolite = false; break;

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值