JUC(二)

1、wait notify

  1. Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING

  2. 状态 BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间片

  3. BLOCKED 线程会在 Owner 线程释放锁时唤醒

  4. WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入 EntryList 重新竞争

api:

  1. obj.wait() 让进入 object 监视器的线程到 waitSet 等待 (带参数的表示等待的时间,时间过到了就不等待直接清醒)

  2. obj.notify() 在 object 上正在 waitSet 等待的线程中挑一个唤醒

  3. obj.notifyAll() 让 object 上正在 waitSet 等待的线程全部唤醒

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

1.1、wait和sleep的区别

sleepwait
属于线程中的方法是Object的方法
不需要和synchronized配合使用需要和synchronized一起使用
在睡眠的同时不会释放锁对象等待的时候会释放锁对象

1.2、状态之间的转换

情况 1: NEW --> RUNNABLE

当调用 t.start() 方法时,由 NEW --> RUNNABL

情况 2: RUNNABLE <--> WAITING

  1. t 线程用 synchronized(obj) 获取了对象锁后,调用 obj.wait() 方法时,t 线程从 RUNNABLE --> WAITING

  2. 调用 obj.notify() , obj.notifyAll() , t.interrupt() 时

    1. 竞争锁成功,t 线程从 WAITING --> RUNNABLE

    2. 竞争锁失败,t 线程从 WAITING --> BLOCKED

情况 3: RUNNABLE <--> WAITING

  1. 当前线程调用 t.join() 方法时,当前线程从 RUNNABLE --> WAITING

    • 注意是当前线程在t 线程对象的监视器上等待

  2. t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 WAITING --> RUNNABLE

情况 4: RUNNABLE <--> WAITING

  1. 当前线程调用 LockSupport.park() 方法会让当前线程从 RUNNABLE --> WAITING

  2. 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,会让目标线程从 WAITING --> RUNNABLE

情况 5: RUNNABLE <--> TIMED_WAITING

  1. t 线程用 synchronized(obj) 获取了对象锁后 调用 obj.wait(long n) 方法时,t 线程从 RUNNABLE --> TIMED_WAITING

  2. t 线程等待时间超过了 n 毫秒,或调用 obj.notify() , obj.notifyAll() , t.interrupt() 时

    1. 竞争锁成功,t 线程从 TIMED_WAITING --> RUNNABLE

    2. 竞争锁失败,t 线程从 TIMED_WAITING --> BLOCKED

情况 6: RUNNABLE <--> TIMED_WAITING

  1. 当前线程调用 t.join(long n) 方法时,当前线程从 RUNNABLE --> TIMED_WAITING

    • 注意是当前线程在t 线程对象的监视器上等待

  2. 当前线程等待时间超过了 n 毫秒,或t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 TIMED_WAITING --> RUNNABLE

情况 7: RUNNABLE <--> TIMED_WAITING

  1. 当前线程调用 Thread.sleep(long n) ,当前线程从 RUNNABLE --> TIMED_WAITING

  2. 当前线程等待时间超过了 n 毫秒,当前线程从 TIMED_WAITING --> RUNNABLE

情况 8: RUNNABLE <--> TIMED_WAITING

  1. 当前线程调用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis) 时,当前线 程从 RUNNABLE --> TIMED_WAITING

  2. 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,或是等待超时,会让目标线程从 TIMED_WAITING--> RUNNABLE

情况 9: RUNNABLE <--> BLOCKED

  1. t 线程用 synchronized(obj) 获取了对象锁时如果竞争失败,从 RUNNABLE --> BLOCKED

  2. 持 obj 锁线程的同步代码块执行完毕,会唤醒该对象上所有 BLOCKED 的线程重新竞争,如果其中 t 线程竞争 成功,从 BLOCKED --> RUNNABLE ,其它失败的线程仍然 BLOCKED

情况 10: RUNNABLE <--> TERMINATED

  1. 当前线程所有代码运行完毕,进入 TERMINATED

2、活跃性

        一个并发应用能够及时执行任务的特性称为活跃性,这一节讲述最常见的一种活跃性问题–死锁,并将简单的介绍另外两种活跃性问题,分别为饥饿和活锁。

2.1、死锁

        不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。一旦出现死锁,整个程序既不会发生异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。

        哲学家就餐问题,五名哲学家围绕者圆桌子,然后一共有五根筷子,分别放在每一个哲学家左右,只有哲学家同时拥有左右两根筷子,那么才可以进行吃饭,如果没有获得两根筷子就一直等待别的哲学家使用完筷子!假如每一个人获得的都是自己右手边的筷子,然后一直等待左手边的人释放筷子,于是乎哲学家到最后全部饿死!

import lombok.extern.slf4j.Slf4j;
import java.util.Random;
​
public class TestDeadLock {
    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 = "c.Philosopher")
class Philosopher extends Thread {
    Chopstick left;
    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();
                }
            }
        }
    }
​
    Random random = new Random();
    private void eat() {
        log.debug("eating...");
        Sleeper.sleep(0.5);
    }
}
​
class Chopstick {
    String name;
​
    public Chopstick(String name) {
        this.name = name;
    }
​
    @Override
    public String toString() {
        return "筷子{" + name + '}';
    }
}

诱发死锁的原因:

  • 互斥条件

  • 占用且等待

  • 不可抢夺(或不可抢占)

  • 循环等待

以上4个条件,同时出现就会触发死锁。

解决死锁:死锁一旦出现,基本很难人为干预,只能尽量规避。可以考虑打破上面的诱发条件。

  1. 针对条件1:互斥条件基本上无法被破坏。因为线程需要通过互斥解决安全问题。

  2. 针对条件2:可以考虑一次性申请所有所需的资源,这样就不存在等待的问题,对共享的资源要么全申请完毕要么全不申请。

  3. 针对条件3:占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放掉已经占用的资源。

  4. 针对条件4:可以将资源改为线性顺序。申请资源时,先申请序号较小的,这样避免循环等待问题。

2.2、活锁

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

@Slf4j(topic = "c.TestLiveLock")
public class TestLiveLock {
    static volatile int count = 10;
    static final Object lock = new Object();
​
    public static void main(String[] args) {
        new Thread(() -> {
            // 期望减到 0 退出循环
            while (count > 0) {
                sleep(0.2);
                count--;
                log.debug("count: {}", count);
            }
        }, "t1").start();
        new Thread(() -> {
            // 期望超过 20 退出循环
            while (count < 20) {
                sleep(0.2);
                count++;
                log.debug("count: {}", count);
            }
        }, "t2").start();
    }
}

2.3、饥饿

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

3、ReentrantLock

相对于 synchronized 它具备如下特点

  1. 可中断

  2. 可以设置超时时间

  3. 可以设置为公平锁

  4. 支持多个条件变量

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

// 获取锁
reentrantLock.lock();
try {
 // 临界区
} finally {
 // 释放锁
 reentrantLock.unlock();
}

可重入

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

@Slf4j(topic = "c.TestReentrant")
public class TestReentrant {
    static ReentrantLock lock = new ReentrantLock();
​
    public static void main(String[] args) {
        method1();
    }
​
    public static void method1() {
        lock.lock();
        try {
            log.debug("execute method1");
            method2();
        } finally {
            lock.unlock();
        }
    }
​
    public static void method2() {
        lock.lock();
        try {
            log.debug("execute method2");
            method3();
        } finally {
            lock.unlock();
        }
    }
​
    public static void method3() {
        lock.lock();
        try {
            log.debug("execute method3");
        } finally {
            lock.unlock();
        }
    }
}

可打断

@Slf4j(topic = "c.TestInterrupt")
public class TestInterrupt {
    public static void main(String[] args) {
        test2();
    }
​
    private static void test2() {
        ReentrantLock lock = new ReentrantLock();
​
        Thread t1 = new Thread(() -> {
            log.debug("启动...");
            lock.lock();
            try {
                log.debug("获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
​
​
        lock.lock();
        log.debug("获得了锁");
        t1.start();
        try {
            sleep(1);
            t1.interrupt();
            log.debug("执行打断");
            sleep(1);
        } finally {
            log.debug("释放了锁");
            lock.unlock();
        }
    }
​
    private static void test1() {
        ReentrantLock lock = new ReentrantLock();
​
        Thread t1 = new Thread(() -> {
            log.debug("启动...");
            try {
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("等锁的过程中被打断");
                return;
            }
            try {
                log.debug("获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
​
​
        lock.lock();
        log.debug("获得了锁");
        t1.start();
        try {
            sleep(1);
            t1.interrupt();
            log.debug("执行打断");
        } finally {
            lock.unlock();
        }
    }
}

锁超时:

@Slf4j(topic = "c.TestTimeout")
public class TestTimeout {
    public static void main(String[] args) {
        test1();
    }
​
    private static void test1() {
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            log.debug("启动...");
            try {
                if (!lock.tryLock(1, TimeUnit.SECONDS)) {
                    log.debug("获取等待 1s 后失败,返回");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                log.debug("获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
​
        lock.lock();
        log.debug("获得了锁");
        t1.start();
        try {
            sleep(2);
        } finally {
            lock.unlock();
        }
    }
    private static void test2() {
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            log.debug("启动...");
            if (!lock.tryLock()) {
                log.debug("获取立刻失败,返回");
                return;
            }
            try {
                log.debug("获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
​
        lock.lock();
        log.debug("获得了锁");
        t1.start();
        try {
            sleep(2);
        } finally {
            lock.unlock();
        }
    }
}

公平锁:

公平锁是为解决饥饿问题,防止线程一直得不到锁!

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值