网站开发语言识别,网站备案 上线,网站备案 ip,wordpress post data多把锁
一间大屋子有两个功能#xff1a;睡觉、学习#xff0c;互不相干。 现在小南要学习#xff0c;小女要睡觉#xff0c;但如果只用一间屋子#xff08;一个对象锁#xff09;的话#xff0c;那么并发度很低 解决方法是准备多个房间#xff08;多个对象锁#xf…多把锁
一间大屋子有两个功能睡觉、学习互不相干。 现在小南要学习小女要睡觉但如果只用一间屋子一个对象锁的话那么并发度很低 解决方法是准备多个房间多个对象锁 例子
class BigRoom {private final Object studyRoom new Object();private final Object bedRoom new Object();public void sleep() {synchronized (bedRoom) {log.debug(sleeping 2 小时);Sleeper.sleep(2);}}public void study() {synchronized (studyRoom) {log.debug(study 1 小时);Sleeper.sleep(1);}}}将锁的粒度细分
好处是可以增强并发度坏处如果一个线程需要同时获得多把锁就容易发生死锁
活跃性
死锁
有这样的情况一个线程需要同时获取多把锁这时就容易发生死锁 t1 线程 获得 A对象 锁接下来想获取 B对象 的锁 t2 线程 获得 B对象 锁 接下来想获取 A对象 的锁 例
Object A new Object();Object B new Object();Thread t1 new Thread(() - {synchronized (A) {log.debug(lock A);sleep(1);synchronized (B) {log.debug(lock B);log.debug(操作...);}}}, t1);Thread t2 new Thread(() - {synchronized (B) {log.debug(lock B);sleep(0.5);synchronized (A) {log.debug(lock A);
log.debug(操作...);}}}, t2);t1.start();t2.start();结果
12:22:06.962 [t2] c.TestDeadLock - lock B
12:22:06.962 [t1] c.TestDeadLock - lock A 定位死锁
检测死锁可以使用 jconsole工具或者使用 jps 定位进程 id再用 jstack 定位死锁
cmd jpsPicked up JAVA_TOOL_OPTIONS: -Dfile.encodingUTF-812320 Jps22816 KotlinCompileDaemon33200 TestDeadLock // JVM 进程
11508 Main28468 Launcher cmd jstack 33200Picked up JAVA_TOOL_OPTIONS: -Dfile.encodingUTF-82018-12-29 05:51:40Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.91-b14 mixed mode):DestroyJavaVM #13 prio5 os_prio0 tid0x0000000003525000 nid0x2f60 waiting on condition
[0x0000000000000000]java.lang.Thread.State: RUNNABLEThread-1 #12 prio5 os_prio0 tid0x000000001eb69000 nid0xd40 waiting for monitor entry
[0x000000001f54f000]java.lang.Thread.State: BLOCKED (on object monitor)at thread.TestDeadLock.lambda$main$1(TestDeadLock.java:28)- waiting to lock 0x000000076b5bf1c0 (a java.lang.Object)- locked 0x000000076b5bf1d0 (a java.lang.Object)at thread.TestDeadLock$$Lambda$2/883049899.run(Unknown Source)at java.lang.Thread.run(Thread.java:745)Thread-0 #11 prio5 os_prio0 tid0x000000001eb68800 nid0x1b28 waiting for monitor entry
[0x000000001f44f000]java.lang.Thread.State: BLOCKED (on object monitor)at thread.TestDeadLock.lambda$main$0(TestDeadLock.java:15)- waiting to lock 0x000000076b5bf1d0 (a java.lang.Object)- locked 0x000000076b5bf1c0 (a java.lang.Object)at thread.TestDeadLock$$Lambda$1/495053715.run(Unknown Source)at java.lang.Thread.run(Thread.java:745)// 略去部分输出
Found one Java-level deadlock:Thread-1:waiting to lock monitor 0x000000000361d378 (object 0x000000076b5bf1c0, a java.lang.Object),which is held by Thread-0Thread-0:waiting to lock monitor 0x000000000361e768 (object 0x000000076b5bf1d0, a java.lang.Object),which is held by Thread-1Java stack information for the threads listed above:Thread-1:at thread.TestDeadLock.lambda$main$1(TestDeadLock.java:28)- waiting to lock 0x000000076b5bf1c0 (a java.lang.Object)- locked 0x000000076b5bf1d0 (a java.lang.Object)at thread.TestDeadLock$$Lambda$2/883049899.run(Unknown Source)at java.lang.Thread.run(Thread.java:745)Thread-0:at thread.TestDeadLock.lambda$main$0(TestDeadLock.java:15)- waiting to lock 0x000000076b5bf1d0 (a java.lang.Object)- locked 0x000000076b5bf1c0 (a java.lang.Object)at thread.TestDeadLock$$Lambda$1/495053715.run(Unknown Source)at java.lang.Thread.run(Thread.java:745)Found 1 deadlock. 避免死锁要注意加锁顺序另外如果由于某个线程进入了死循环导致其它线程一直等待对于这种情况 linux 下可以通过 top 先定位到 CPU 占用高的 Java 进程再利用 top -Hp 进程id 来定位是哪个线程最后再用 jstack 排查
活锁
活锁出现在两个线程互相改变对方的结束条件最后谁也无法结束例如
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()},
}饥饿
饥饿定义为一个线程由于优先级太低始终得不到 CPU 调度执行也不能够结束饥饿的情况不 易演示讲读写锁时会涉及饥饿问题 下面我讲一下我遇到的一个线程饥饿的例子先来看看使用顺序加锁的方式解决之前的死锁问题
顺序加锁的解决方案
ReentrantLock
相对于 synchronized 它具备如下特点
可中断可以设置超时时间可以设置为公平锁支持多个条件变量
与 synchronized 一样都支持可重入 基本语法
// 获取锁
reentrantLock.lock();try {// 临界区
} finally {// 释放锁
reentrantLock.unlock();}
可重入 可重入是指同一个线程如果首次获得了这把锁那么因为它是这把锁的拥有者因此有权利再次获取这把锁如果是不可重入锁那么第二次获得锁时自己也会被锁挡
可打断 一个线程在等待锁的过程中可以被其他线程打断而提前结束等待
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();}输出
18:02:40.520 [main] c.TestInterrupt - 获得了锁
18:02:40.524 [t1] c.TestInterrupt - 启动...
18:02:41.530 [main] c.TestInterrupt - 执行打断
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 cn.onenewcode.n4.reentrant.TestInterrupt.lambda$main$0(TestInterrupt.java:17)
at java.lang.Thread.run(Thread.java:748)
18:02:41.532 [t1] c.TestInterrupt - 等锁的过程中被打断锁超时 如果某个线程在规定的时间内无法获取到锁就会超时放弃.可以一定限度防止死锁。
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();}输出
18:19:40.537 [main] c.TestTimeout - 获得了锁
18:19:40.544 [t1] c.TestTimeout - 启动...
18:19:41.547 [t1] c.TestTimeout - 获取等待 1s 后失败返回 不公平锁 表示获取锁的抢占机制是随机获取锁的和公平锁不一样的就是先来的不一定能拿到锁 有可能一直拿不到锁所以结果不公平。
ReentrantLock 默认是不公平的
ReentrantLock lock new ReentrantLock(false);lock.lock();for (int i 0; i 500; i) {new Thread(() - {lock.lock();try {System.out.println(Thread.currentThread().getName() running...);}
finally {lock.unlock();}}, t i).start();
} // 1s 之后去争抢锁
Thread.sleep(1000);new Thread(() - {System.out.println(Thread.currentThread().getName() start...);lock.lock();try {System.out.println(Thread.currentThread().getName() running...);} finally {lock.unlock();}}, 强行插入).start();lock.unlock();强行插入有机会在中间输出
t39 running...
t40 running...
t41 running...
t42 running...
t43 running...
强行插入 start...
强行插入 running...
t44 running...
t45 running...
t46 running...
t47 running...
t49 running... 条件变量 synchronized 中也有条件变量就是我们讲原理时那个 waitSet 休息室当条件不满足时进入 waitSet 等待 ReentrantLock 的条件变量比 synchronized 强大之处在于它是支持多个条件变量的这就好比
synchronized 是那些不满足条件的线程都在一间休息室等消息而 ReentrantLock 支持多间休息室有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒
使用要点
await 前需要获得锁await 执行后会释放锁进入 conditionObject 等待await 的线程被唤醒或打断、或超时取重新竞争 lock 锁竞争 lock 锁成功后从 await 后继续执行
static ReentrantLock lock new ReentrantLock();static Condition waitCigaretteQueue lock.newCondition();static Condition waitbreakfastQueue lock.newCondition();static volatile boolean hasCigrette false;static volatile boolean hasBreakfast false;public static void main(String[] args) {new Thread(() - {try {lock.lock();while (!hasCigrette) {try {waitCigaretteQueue.await();}
catch (InterruptedException e) {e.printStackTrace();}}log.debug(等到了它的烟);}
finally {lock.unlock();}}).start();new Thread(() - {try {lock.lock();while (!hasBreakfast) {try {waitbreakfastQueue.await();}
catch (InterruptedException e) {e.printStackTrace();}}log.debug(等到了它的早餐);}
finally {lock.unlock();}}).start();sleep(1);sendBreakfast();sleep(1);sendCigarette();}private static void sendCigarette() {lock.lock();try {log.debug(送烟来了);hasCigrette true;waitCigaretteQueue.signal();}
finally {lock.unlock();}}private static void sendBreakfast() {lock.lock();try {log.debug(送早餐来了);hasBreakfast true;waitbreakfastQueue.signal();}
finally {lock.unlock();}}输出
18:52:27.680 [main] c.TestCondition - 送早餐来了
18:52:27.682 [Thread-1] c.TestCondition - 等到了它的早餐
18:52:28.683 [main] c.TestCondition - 送烟来了
18:52:28.683 [Thread-0] c.TestCondition - 等到了它的烟