并发编程(五)

92 篇文章 0 订阅
10 篇文章 0 订阅

死锁

  • 多个同步块使用同一个锁对象, 会严重降低程序的并发量. 此时可以通过多把锁(锁颗粒度细分化)方式, 给每个同步块(功能块)都分配专有锁对象, 来提升并发量. 但同一个线程获取多把锁(多个 synchronized同步块), 会容易引起死锁

  • 死锁演示代码 1:


public class App {
    public static void main(String[] args) {
        final Object A = new Object();
        final Object B = new Object();
        Thread t1 = new Thread(() -> {
            synchronized(A) {
                System.out.println("t1 lock A");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized(B) { // 当前线程(t1), 阻塞等待 t2线程的解锁
                    System.out.println("t1 lock B");
                }
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            synchronized(B) {
                System.out.println("t2 lock B");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized(A) { // 当前线程(t2), 阻塞等待 t1线程的解锁
                    System.out.println("t2 lock A");
                }
            }
        }, "t2");
        t1.start();
        t2.start();
    }
}
t1 lock A
t2 lock B
被锁

  • 哲学家就餐问题:

在这里插入图片描述

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

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

// 筷子类
class Chopstick {
    String name;
    public Chopstick(String name) {
        this.name = name;
    }
    
    @Override
    public String toString() {
        return "筷子{" + name + '}';
    }
}
// 哲学家类
class Philosopher extends Thread {
    Chopstick left;
    Chopstick right;
    public Philosopher(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    private void eat() {
        System.out.println(Thread.currentThread().getName() + " is eating...");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true) {
            synchronized (left) { // 获得左手筷子
                synchronized (right) { // 获得右手筷子
                    eat(); // 吃饭
                } // 放下右手筷子

            } // 放下左手筷子
        }
    }
}

public class App {
    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();
		// 调换加锁顺序, 可以避免死锁
		// new Philosopher("阿基米德", c1, c5).start(); 
    }
}
苏格拉底 is eating...
亚里士多德 is eating...
阿基米德 is eating...
...
被锁

  • 定位死锁:

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


C:\Users\Shawn Jeon>jps
11688 App
15720
344 Launcher
6844 Jps

C:\Users\Shawn Jeon>jstack 11688
2021-03-11 10:06:30
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.121-b13 mixed mode):

"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x0000000003622800 nid=0x2b60 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"t2" #12 prio=5 os_prio=0 tid=0x000000001c7f0800 nid=0x2594 waiting for monitor entry [0x000000001d1ee000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at org.example.base.threads.App.lambda$main$1(App.java:85)
        - waiting to lock <0x00000007810af9b0> (a java.lang.Object)
        - locked <0x00000007810af9c0> (a java.lang.Object)
        at org.example.base.threads.App$$Lambda$2/2074407503.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)

"t1" #11 prio=5 os_prio=0 tid=0x000000001bfe6000 nid=0xb1c waiting for monitor entry [0x000000001d0ee000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at org.example.base.threads.App.lambda$main$0(App.java:72)
        - waiting to lock <0x00000007810af9c0> (a java.lang.Object)
        - locked <0x00000007810af9b0> (a java.lang.Object)
        at org.example.base.threads.App$$Lambda$1/1149319664.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)

...
...
省略
...


Found one Java-level deadlock:
=============================
"t2":
  waiting to lock monitor 0x000000000371a3e8 (object 0x00000007810af9b0, a java.lang.Object),
  which is held by "t1"
"t1":
  waiting to lock monitor 0x000000000371cbc8 (object 0x00000007810af9c0, a java.lang.Object),
  which is held by "t2"

Java stack information for the threads listed above:
===================================================
"t2":
        at org.example.base.threads.App.lambda$main$1(App.java:85)
        - waiting to lock <0x00000007810af9b0> (a java.lang.Object)
        - locked <0x00000007810af9c0> (a java.lang.Object)
        at org.example.base.threads.App$$Lambda$2/2074407503.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)
"t1":
        at org.example.base.threads.App.lambda$main$0(App.java:72)
        - waiting to lock <0x00000007810af9c0> (a java.lang.Object)
        - locked <0x00000007810af9b0> (a java.lang.Object)
        at org.example.base.threads.App$$Lambda$1/1149319664.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)

Found 1 deadlock.

活锁

  • 两个线程互相改动对方的结束条件, 导致双方都无法结束

public class App15 {
    static volatile int count = 10;
    public static void main(String[] args) {
        new Thread(() -> {
            while (count > 0) { // 等于或小于0, 则退出循环
                try {
                    TimeUnit.MILLISECONDS.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println(Thread.currentThread().getName() + ", count: " + count);
            }
        }, "t1").start();
        new Thread(() -> {
            while (count < 20) { // 大于或等于20, 则退出循环
                try {
                    TimeUnit.MILLISECONDS.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count++;
                System.out.println(Thread.currentThread().getName() + ", count: " + count);
            }
        }, "t2").start();
    }
}

饥饿

  • 一个线程由于优先级太低, 始终得不到 CPU调度执行(拿不到锁), 也无法结束线程

ReentrantLock

  • 特点: 1. 支持重入锁 2. 可以打断 3. 可以设置超时时间 4. 可以设置为公平锁 5. 支持多个条件变量

可重入

  • 同一个线程可以重复加相同的锁. 然后解锁时, 从最近加的锁开始解锁

public class App {
    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        method1();
    }

    public static void method1() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + ", execute method1");
            method2();
        } finally {
            lock.unlock();
        }
    }

    public static void method2() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + ", execute method2");
            method3();
        } finally {
            lock.unlock();
        }
    }

    public static void method3() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + ", execute method3");
        } finally {
            lock.unlock();
        }
    }
}
main, execute method1
main, execute method2
main, execute method3

可打断

  • 当锁有竞争时, 执行 lockInterruptibly()锁会被阻塞, 此时可以通过 interrupt()方法停止阻塞
    * 如果没有竞争, 则会通过 lock()方法获取锁

public class App {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + ", 启动...");
            try {
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println(Thread.currentThread().getName() + ", 锁阻塞过程中被打断!");
                return;
            }

            // lock.lock(); 此种一般锁是无法被 interrupt()方法, 所中断的
            try {
                System.out.println(Thread.currentThread().getName() + ", 获得了锁!");
            } finally {
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        System.out.println(Thread.currentThread().getName() + ", 获得了锁!");
        t1.start();

        try {
            TimeUnit.SECONDS.sleep(1);
            t1.interrupt();
            System.out.println(Thread.currentThread().getName() + ", 执行打断!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
main, 获得了锁!
t1, 启动...
main, 执行打断!
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 org.example.base.threads.App.lambda$main$0(App.java:17)
	at java.lang.Thread.run(Thread.java:748)
t1, 锁阻塞过程中被打断!

锁超时

  • tryLock()方法有竞争时, 不会阻塞, 而立刻返回 false. 不进行加锁

public class App {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + ", 启动...");
            // 有竞争时, 不会阻塞, 而直接返回 false
            if (!lock.tryLock()) {
                System.out.println(Thread.currentThread().getName() + ", 获取锁失败!");
                return;
            }
            try {
                System.out.println(Thread.currentThread().getName() + ", 获得了锁!");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        System.out.println(Thread.currentThread().getName() + ", 获得了锁!");
        t1.start();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
main, 获得了锁!
t1, 启动...
t1, 获取锁失败!

  • tryLock(long timeout, TimeUnit unit)方法, 超过设定的时间后失败

public class App {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + ", 启动...");
            try {
                if (!lock.tryLock(1, TimeUnit.SECONDS)) {
                    System.out.println(Thread.currentThread().getName() + ", 获取锁等待1s后失败!");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                System.out.println(Thread.currentThread().getName() + ", 获得了锁!");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        System.out.println(Thread.currentThread().getName() + ", 获得了锁!");
        t1.start();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
main, 获得了锁!
t1, 启动...
t1, 获取锁等待1s后失败!

通过 ReentrantLock解决哲学家就餐问题


// 筷子类
class Chopstick extends ReentrantLock {
    String name;
    public Chopstick(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "筷子{" + name + '}';
    }
}
// 哲学家类
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) {
            if (left.tryLock()) { // 尝试获得左手筷子
                try {
                    if (right.tryLock()) { // 尝试获得右手筷子
                        try {
                            eat();
                        } finally {
                            right.unlock();
                        }
                    }
                } finally {
                    left.unlock();
                }
            }
        }
    }
    private void eat() {
        System.out.println(Thread.currentThread().getName() + " is eating...");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class App {
    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();
    }
}

公平锁

  • ReentrantLock(false)默认是不公平的

public class App {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        lock.lock();
        for (int i = 0; i < 500; i++) {
            new Thread(() -> {
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + " is running...");
                } finally {
                    lock.unlock();
                }
            }, "t" + i).start();
        }

        // 1s 之后去争抢锁
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " start...");
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " is running...");
            } finally {
                lock.unlock();
            }
        }, "强行插入").start();
        lock.unlock();
    }
}
t1 is running...
t0 is running...
t2 is running...
t3 is running...
t4 is running...
强行插入 start...
强行插入 is running...
t5 is running...
...
...

* 使用了不公平锁时, 会时不时的可以强行插入成功

  • new ReentrantLock(true)改为公平锁后, 强行插入总会是最后
    * 注: 公平锁会降低并发量, 如不是非必要, 则建议用不公平锁

条件变量

  • synchronized中也有条件变量, 也就是锁对象的 Monitor.waitSet(休息室), 当条件不满足时进入 waitSet等待, 与此对应的 ReentrantLock的条件变量是 Condition类, 不同于 waitSet, 它支持多个条件变量(多间休息室)

public class App {
    static ReentrantLock lock = new ReentrantLock();
    // 等烟的休息室
    static Condition waitCigaretteQueue = lock.newCondition();
    // 等外卖的休息室
    static Condition waitTakeoutQueue = lock.newCondition();
    static volatile boolean hasCigrette = false;
    static volatile boolean hasBreakfast = false;
    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            try {
                lock.lock();
                while (!hasCigrette) {
                    try {
                        waitCigaretteQueue.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + ", 等到了他的烟!");
            } finally {
                lock.unlock();
            }
        }, "小王").start();

        new Thread(() -> {
            try {
                lock.lock();
                while (!hasBreakfast) {
                    try {
                        waitTakeoutQueue.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + ", 等到了他的外卖!");
            } finally {
                lock.unlock();
            }
        }, "小张").start();

        TimeUnit.SECONDS.sleep(1);
        sendBreakfast();

        TimeUnit.SECONDS.sleep(1);
        sendCigarette();
    }

    private static void sendCigarette() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + ", 送烟来了!");
            hasCigrette = true;
            waitCigaretteQueue.signal();
        } finally {
            lock.unlock();
        }
    }

    private static void sendBreakfast() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + ", 送外卖来了!");
            hasBreakfast = true;
            waitTakeoutQueue.signal();
        } finally {
            lock.unlock();
        }
    }
}
main, 送外卖来了!
小张, 等到了他的外卖!
main, 送烟来了!
小王, 等到了他的烟!

如果您觉得有帮助,欢迎点赞哦 ~ 谢谢!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值