synchronized关键字作用于代码块修饰成员方法,锁住this对象修饰静态方法,锁住方法所在类的class对象案例’‘线程八锁’’java对象头使用JOL工具类,打印对象头monitor锁synchronized优化wait和notifyAPI 使用sleep(long n)和wait(long n)的区别wait,notify的正确使用方式设计模式之保护性暂停原理之 join生产者消费者模式多把锁(细粒度锁)锁的活跃性死锁定位死锁哲学家就餐问题活锁饥饿ReentrantLock
认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。
synchronized关键字和Lock的实现类都是悲观锁
synchronized关键字
为了避免临界区的竞态条件发生,有多种手段可以达到目的。
- 阻塞式的解决方案:synchronized,Lock
- 非阻塞式的解决方案:原子变量
用阻塞式的解决方案:synchronized,来解决上述问题,即俗称的【对象锁】,它采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换,
synchronized
实现使用的是monitorenter
和monitorexit
指令 javap -c ***.class
文件反编译作用于代码块
synchronized(锁对象){ //临界区代码 }
案例
@Slf4j(topic = "c.Test17") public class Test17 { public static void main(String[] args) throws InterruptedException { Room room = new Room(); Thread t1 = new Thread(() -> { for (int i = 0; i < 5000; i++) { room.increment(); } }, "t1"); Thread t2 = new Thread(() -> { for (int i = 0; i < 5000; i++) { room.decrement(); } }, "t2"); t1.start(); t2.start(); t1.join(); t2.join(); log.debug("{}", room.getCounter()); } } class Room { private int counter = 0; public synchronized void increment() { counter++; } public synchronized void decrement() { counter--; } public synchronized int getCounter() { return counter; } }
synchronized实际是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断
修饰成员方法,锁住this对象
class Test { public synchronized void increment() { } }等价于 class Test{ public void test(){ synchronized(this){ } } }
调用指令将会检查方法的
ACC_SYNCHRONIZED
访问标志是否被设置。如果设置了,执行线程会将先持有monitor然后再执行方法,
最后在方法完成(无论是正常完成还是非正常完成)时释放 monitor修饰静态方法,锁住方法所在类的class对象
class Test { public synchronized static void increment() { } }等价于 class Test{ public void test(){ synchronized(Test.class){ } } }
ACC_STATIC, ACC_SYNCHRONIZED访问标志区分该方法是否静态同步方法
案例’‘线程八锁’’
1
@Slf4j(topic = "c.Test8Locks") public class Test8Locks { public static void main(String[] args) { Number n1 = new Number(); new Thread(() -> { log.debug("begin"); n1.a(); }).start(); new Thread(() -> { log.debug("begin"); n1.b(); }).start(); } } @Slf4j(topic = "c.Number") class Number{ public synchronized void a() { log.debug("1"); } public synchronized void b() { log.debug("2"); } } //锁对象都为n1
2
@Slf4j(topic = "c.Test8Locks") public class Test8Locks { public static void main(String[] args) { Number n1 = new Number(); new Thread(() -> { log.debug("begin"); n1.a(); }).start(); new Thread(() -> { log.debug("begin"); n1.b(); }).start(); } } @Slf4j(topic = "c.Number") class Number{ public synchronized void a() { sleep(1); log.debug("1"); } public synchronized void b() { log.debug("2"); } } //output 1秒后1,2或者21秒后1 //sleep并不会释放锁对象
3
@Slf4j(topic = "c.Test8Locks") public class Test8Locks { 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(); } } @Slf4j(topic = "c.Number") class Number { public synchronized void a() { sleep(1); log.debug("1"); } public synchronized void b() { log.debug("2"); } public void c() {//c未上锁和,a,b互不影响,a,b同一对象锁互斥 log.debug("3"); } } //output:3,1秒后1,2或3,2,1秒后1或2,3,1秒,1或 1秒,1,3,2
4
@Slf4j(topic = "c.Test8Locks") public class Test8Locks { public static void main(String[] args) { Number n1 = new Number(); Number n2 = new Number(); new Thread(() -> { n1.a(); }).start(); new Thread(() -> { n2.b(); }).start(); } @Slf4j(topic = "c.Number") class Number { public synchronized void a() { sleep(1); log.debug("1"); } public synchronized void b() { log.debug("2"); } }//output:2,1秒后1
5
@Slf4j(topic = "c.Test8Locks") public class Test8Locks { public static void main(String[] args) { Number n1 = new Number(); new Thread(() -> { n1.a(); }).start(); new Thread(() -> { n1.b(); }).start(); } @Slf4j(topic = "c.Number") class Number { public static synchronized void a() { //锁对象为Number.class sleep(1); log.debug("1"); } public synchronized void b() { log.debug("2"); } }//output:2,1秒后1
6
@Slf4j(topic = "c.Test8Locks") public class Test8Locks { public static void main(String[] args) { Number n1 = new Number(); new Thread(() -> { n1.a(); }).start(); new Thread(() -> { n1.b(); }).start(); } @Slf4j(topic = "c.Number") class Number { public static synchronized void a() { //锁对象为Number.class sleep(1); log.debug("1"); } public static synchronized void b() { log.debug("2"); } }//output:2,1秒后1或者1秒后1,2
7
@Slf4j(topic = "c.Test8Locks") public class Test8Locks { public static void main(String[] args) { Number n1 = new Number(); Number n2 = new Number(); new Thread(() -> { n1.a(); }).start(); new Thread(() -> { n2.b(); }).start(); } @Slf4j(topic = "c.Number") class Number { public static synchronized void a() { //锁对象为Number.class sleep(1); log.debug("1"); } public synchronized void b() { log.debug("2"); } }//output:2,1秒后1
8
@Slf4j(topic = "c.Test8Locks") public class Test8Locks { public static void main(String[] args) { Number n1 = new Number(); Number n2 = new Number(); new Thread(() -> { n1.a(); }).start(); new Thread(() -> { n2.b(); }).start(); } @Slf4j(topic = "c.Number") class Number { public static synchronized void a() { //锁对象为Number.class sleep(1); log.debug("1"); } public static synchronized void b() { log.debug("2"); } }//output:2,1秒后1或者1秒后1,2
java对象头
Java对象的对象头由
mark word
和 klass pointer
两部分组成,mark word
存储了同步状态、标识、hashcode、GC状态等等。klass pointer
存储对象的类型指针,该指针指向它的类元数据值得注意的是,如果应用的对象过多,使用64位的指针将浪费大量内存。64位的JVM比32位的JVM多耗费50%的内存。
我们现在使用的64位 JVM会默认使用选项
+UseCompressedOops
开启指针压缩,将指针压缩至32位。64位虚拟机
lock: 锁状态标记位,该标记的值不同,整个mark word表示的含义不同。
biased_lock:偏向锁标记,为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。
age:Java GC标记位对象年龄。
identity_hashcode
:对象标识Hash码,采用延迟加载技术。当对象使用HashCode()计算后,并会将结果写到该对象头中。当对象被锁定时,该值会移动到线程Monitor中。thread
:持有偏向锁的线程ID和其他信息。这个线程ID并不是JVM分配的线程ID号,和Java Thread中的ID是两个概念。epoch:偏向时间戳。
ptr_to_lock_record
:指向栈中锁记录的指针。ptr_to_heavyweight_monitor
:指向线程Monitor的指针。使用JOL工具类,打印对象头
<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.10</version> </dependency>
A a = new A(); System.out.println(ClassLayout.parseInstance(a).toPrintable());
输出的第一行内容和锁状态内容对应
unused:1 | age:4 | biased_lock:1 | lock:2
0 0000 0 01
代表A对象正处于无锁状态第三行中表示的是被指针压缩为32位的
klass pointer
第四行则是我们创建的A对象属性信息 1字节的boolean值
第五行则代表了对象的对齐字段 为了凑齐64位的对象,对齐字段占用了3个字节,24bit
简化JOL输出
SimpleClassLayout
public class SimpleClassLayout { private final ClassData classData; private final SortedSet<FieldLayout> fields; private final int headerSize; private final long size; public SimpleClassLayout(ClassData classData, SortedSet<FieldLayout> fields, int headerSize, long instanceSize, boolean check) { this.classData = classData; this.fields = fields; this.headerSize = headerSize; this.size = instanceSize; } public static SimpleClassLayout parseInstance(Object instance) { return parseInstance(instance, new SimpleLayouter()); } public static SimpleClassLayout parseInstance(Object instance, SimpleLayouter layouter) { return layouter.layout(ClassData.parseInstance(instance)); } public String toPrintSimple() { return toPrintSimple(classData.instance(), true); } public String toPrintSimple(Boolean isMoreInfo) { return toPrintSimple(classData.instance(), isMoreInfo); } public SortedSet<FieldLayout> fields() { return fields; } public String toPrintSimple(Object instance, Boolean isMoreInf) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); String MSG_GAP = "(alignment/padding gap)"; String MSG_NEXT_GAP = "(loss due to the next object alignment)"; int maxTypeLen = "TYPE".length(); for (FieldLayout f : fields()) { maxTypeLen = Math.max(f.typeClass().length(), maxTypeLen); } maxTypeLen += 2; int maxDescrLen = Math.max(MSG_GAP.length(), MSG_NEXT_GAP.length()); for (FieldLayout f : fields()) { maxDescrLen = Math.max(f.shortFieldName().length(), maxDescrLen); } maxDescrLen += 2; if (instance != null) { VirtualMachine vm = VM.current(); if (isMoreInf) { pw.print("\n64位Mark Word信息: "); } String lockSign = ""; for (long off = 4; off >= 0; off -= 4) { int word = vm.getInt(instance, off); pw.printf(toBinary((word >> 24) & 0xFF) + " " + toBinary((word >> 16) & 0xFF) + " " + toBinary((word >> 8) & 0xFF) + " " + toBinary((word >> 0) & 0xFF) + " " ); if (off == 0) { String last8 = toBinary((word >> 0) & 0xFF); lockSign = last8.substring(last8.length() - 3); } } if (isMoreInf) { pw.print("\n锁标识位为: " + lockSign); String lockName = getLockName(lockSign); pw.print("\n锁状态为: " + lockName); } } else { pw.printf(" %6d %5d %" + maxTypeLen + "s %-" + maxDescrLen + "s %s%n", 0, headerSize(), "", "(object header)", "N/A"); } pw.close(); return sw.toString(); } /** * 获取锁状态 * * @param lockSign * @return */ private String getLockName(String lockSign) { if ("000".equals(lockSign)) { return "轻量级锁、自旋锁"; } if ("010".equals(lockSign)) { return "重量级锁"; } if ("011".equals(lockSign)) { return "GC标记信息"; } if ("101".equals(lockSign)) { return "偏向锁"; } if ("001".equals(lockSign)) { return "无锁"; } return ""; } /** v * Answer header size * * @return header size */ public int headerSize() { return headerSize; } // very ineffective, so what? private static String toHex(int x) { StringBuilder s = new StringBuilder(Integer.toHexString(x)); int deficit = 2 - s.length(); for (int c = 0; c < deficit; c++) { s.insert(0, "0"); } return s.toString(); } // very ineffective, so what? private static String toBinary(int x) { StringBuilder s = new StringBuilder(Integer.toBinaryString(x)); int deficit = 8 - s.length(); for (int c = 0; c < deficit; c++) { s.insert(0, "0"); } return s.toString(); } }
SimpleLayouter
public class SimpleLayouter { public SimpleClassLayout layout(ClassData data) { VirtualMachine vm = VM.current(); if (data.isArray()) { // special case of arrays int base = vm.arrayBaseOffset(data.arrayComponentType()); int scale = vm.arrayIndexScale(data.arrayComponentType()); long instanceSize = MathUtil.align(base + data.arrayLength() * scale, vm.objectAlignment()); SortedSet<FieldLayout> result = new TreeSet<>(); result.add(new FieldLayout(FieldData.create(data.arrayClass(), "<elements>", data.arrayComponentType()), base, scale * data.arrayLength())); return new SimpleClassLayout(data, result, vm.arrayHeaderSize(), instanceSize, false); } Collection<FieldData> fields = data.fields(); SortedSet<FieldLayout> result = new TreeSet<>(); for (FieldData f : fields) { result.add(new FieldLayout(f, f.vmOffset(), vm.sizeOfField(f.typeClass()))); } long instanceSize; if (result.isEmpty()) { instanceSize = vm.objectHeaderSize(); } else { FieldLayout f = result.last(); instanceSize = f.offset() + f.size(); // TODO: This calculation is incorrect if there is a trailing @Contended field, or the instance is @Contended } instanceSize = MathUtil.align(instanceSize, vm.objectAlignment()); return new SimpleClassLayout(data, result, vm.objectHeaderSize(), instanceSize, true); } @Override public String toString() { return "Current VM Layout"; } }
使用
monitor锁
Monitor被翻译为监视器或管程
在HotSpot虚拟机中,monitor采用ObjectMonitor实现 ObjectMonitor.java→ObjectMonitor.cpp→objectMonitor.hpp
每个Java对象都可以关联一个Monitor对象(操作系统实现),如果使用synchronized给对象上锁(重量级)之后,该对象头的MarkWord中就可以被设置指向Monitor对象的指针
演示
entryList为链表结构
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
- Thread-2执行完同步代码块的内容,然后唤醒EntryList中等待的线程来竞争锁,竞争是非公平的
- WaitSet中的Thread-0,Thread-1是之前获得过锁,但条件不满足进入WAITING状态的线程
synchronized优化
synchronized优化wait和notify
- Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态
- BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间片
- BLOCKED 线程会在 Owner 线程释放锁时唤醒
- WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入 EntryList 重新竞争
API 使用
- obj.wait() 让进入 object 监视器的线程到 waitSet 等待
- obj.notify() 在 object 上正在 waitSet 等待的线程中挑一个唤醒
- obj.notifyAll() 让 object 上正在 waitSet 等待的线程全部唤醒
它们都是线程之间进行协作的手段,都属于 Object 对象的方法。必须获得此对象的锁,才能调用这几个方法
@Slf4j(topic = "c.TestWaitNotify") public class TestWaitNotify { final static Object obj = new Object(); public static void main(String[] args) { new Thread(() -> { synchronized (obj) { log.debug("执行...."); try { obj.wait(); // 让线程在obj上一直等待下去 } catch (InterruptedException e) { e.printStackTrace(); } log.debug("其它代码...."); } },"t1").start(); new Thread(() -> { synchronized (obj) { log.debug("执行...."); try { obj.wait(); // 让线程在obj上一直等待下去 } catch (InterruptedException e) { e.printStackTrace(); } log.debug("其它代码...."); } },"t2").start(); // 主线程两秒后执行 sleep(0.5); log.debug("唤醒 obj 上其它线程"); synchronized (obj) { // obj.notify(); // 唤醒obj上一个线程 obj.notifyAll(); // 唤醒obj上所有等待线程 } } }
notifyAll结果
20:19:04.789 c.TestWaitNotify [t1] - 执行.... 20:19:04.792 c.TestWaitNotify [t2] - 执行.... 20:19:05.286 c.TestWaitNotify [main] - 唤醒 obj 上其它线程 20:19:05.286 c.TestWaitNotify [t2] - 其它代码.... 20:19:05.286 c.TestWaitNotify [t1] - 其它代码....
notify结果
20:22:28.710 c.TestWaitNotify [t1] - 执行.... 20:22:28.712 c.TestWaitNotify [t2] - 执行.... 20:22:29.210 c.TestWaitNotify [main] - 唤醒 obj 上其它线程 20:22:29.210 c.TestWaitNotify [t1] - 其它代码....
sleep(long n)和wait(long n)的区别
- sleep 是 Thread 方法,而 wait 是 Object 的方法
- sleep 不需要强制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用
- sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁
- 它们的线程状态都为 TIMED_WAITING
static final Object lock = new Object(); public static void main(String[] args) { new Thread(() -> { synchronized (lock) { log.debug("获得锁"); try { // Thread.sleep(20000); lock.wait(20000); } catch (InterruptedException e) { e.printStackTrace(); } } }, "t1").start(); Sleeper.sleep(1); synchronized (lock) { log.debug("获得锁"); } }
wait,notify的正确使用方式
new Thread(() -> { synchronized (room) { log.debug("有烟没?[{}]", hasCigarette); if (!hasCigarette) { log.debug("没烟,先歇会!"); try { room.wait(); } catch (InterruptedException e) } log.debug("有烟没?[{}]", hasCigarette); if (hasCigarette) { log.debug("可以开始干活了"); } else { log.debug("没干成活..."); } } }, "小南").start(); new Thread(() -> { synchronized (room) { Thread thread = Thread.currentThread(); 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(); sleep(1); new Thread(() -> { synchronized (room) { hasTakeout = true; log.debug("外卖到了噢!"); room.notify(); } }, "送外卖的").start(); //输出 //20:53:12.173 [小南] c.TestCorrectPosture - 有烟没?[false] //20:53:12.176 [小南] c.TestCorrectPosture - 没烟,先歇会! //20:53:12.176 [小女] c.TestCorrectPosture - 外卖送到没?[false] //20:53:12.176 [小女] c.TestCorrectPosture - 没外卖,先歇会! //20:53:13.174 [送外卖的] c.TestCorrectPosture - 外卖到了噢! //20:53:13.174 [小南] c.TestCorrectPosture - 有烟没?[false] //20:53:13.174 [小南] c.TestCorrectPosture - 没干成活...
notify 只能随机唤醒一个 WaitSet 中的线程,这时如果有其它线程也在等待,那么就可能唤醒不了正确的线程,称之为
虚假唤醒
解决方法,改为 notifyAllnew Thread(() -> { synchronized (room) { hasTakeout = true; log.debug("外卖到了噢!"); room.notifyAll(); } }, "送外卖的").start(); //输出 //20:55:23.978 [小南] c.TestCorrectPosture - 有烟没?[false] //20:55:23.982 [小南] c.TestCorrectPosture - 没烟,先歇会! //20:55:23.982 [小女] c.TestCorrectPosture - 外卖送到没?[false] //20:55:23.982 [小女] c.TestCorrectPosture - 没外卖,先歇会! //20:55:24.979 [送外卖的] c.TestCorrectPosture - 外卖到了噢! //20:55:24.979 [小女] c.TestCorrectPosture - 外卖送到没?[true] //20:55:24.980 [小女] c.TestCorrectPosture - 可以开始干活了 //20:55:24.980 [小南] c.TestCorrectPosture - 有烟没?[false] //20:55:24.980 [小南] c.TestCorrectPosture - 没干成活...
唤醒了正确的线程的同时也唤醒了无关的线程
用 notifyAll 仅解决某个线程的唤醒问题,但使用 if + wait 判断仅有一次机会,一旦条件不成立,就没有重新判断的机会了
解决方法,用 while + wait,当条件不成立,再次 wait
while (!hasCigarette) { //业务代码 }
wait,notify的正确使用模式
synchronized(lock) { while(条件不成立) { lock.wait(); } // 条件成立的业务代码 } //另一个线程 synchronized(lock) { lock.notifyAll(); }
设计模式之保护性暂停
原理之 join
t1.join()
等价于下面的代码synchronized (t1) { // 调用者线程进入 t1 的 waitSet 等待, 直到 t1 运行结束 while (t1.isAlive()) { t1.wait(0); } }
jdk源码
public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
生产者消费者模式
多把锁(细粒度锁)
多把不相干的锁 一间大屋子有两个功能:睡觉、学习,互不相干。
现在小南要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低
解决方法是准备多个房间(多个对象锁) 例如
@Slf4j(topic = "c.BigRoom") 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); } } }
public class TestMultiLock { public static void main(String[] args) { BigRoom bigRoom = new BigRoom(); new Thread(() -> { bigRoom.study(); },"小南").start(); new Thread(() -> { bigRoom.sleep(); },"小女").start(); } }
将锁的粒度细分好处,是可以增强并发度 .坏处,如果一个线程需要同时获得多把锁,就容易发生死锁
锁的活跃性
死锁
有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。
当线程进入对象的
synchronized
代码块时,便占有了资源,直到它退出该代码块或者调用wait方法,才释放资源,在此期间,其他线程将不能进入该代码块。当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。下面是一个简单的死锁案例
@Slf4j(topic = "c.TestDeadLock") public class TestDeadLock { public static void main(String[] args) { test1(); } private static void test1() { 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(); } }
定位死锁
使用
jps
命令查询Java进程,再用jstack + 进程 查询对应进程Found one Java-level deadlock: ============================= "t2": waiting to lock monitor (object 0x0000000719fa4a10, a java.lang.Object), which is held by "t1" "t1": waiting to lock monitor 0x0000020030ee3208 (object 0x0000000719fa4a20, a java.lang.Object), which is held by "t2" Java stack information for the threads listed above: =================================================== "t2": at cn.itcast.n4.deadlock.TestDeadLock.lambda$test1$1(TestDeadLock.java:32) - waiting to lock <0x0000000719fa4a10> (a java.lang.Object) - locked <0x0000000719fa4a20> (a java.lang.Object) at cn.itcast.n4.deadlock.TestDeadLock$$Lambda$2/1792845110.run(Unknown Source) at java.lang.Thread.run(Thread.java:748) "t1": at cn.itcast.n4.deadlock.TestDeadLock.lambda$test1$0(TestDeadLock.java:21) - waiting to lock <0x0000000719fa4a20> (a java.lang.Object) - locked <0x0000000719fa4a10> (a java.lang.Object) at cn.itcast.n4.deadlock.TestDeadLock$$Lambda$1/897913732.run(Unknown Source) at java.lang.Thread.run(Thread.java:748) Found 1 deadlock.
或者使用jconsole定位
哲学家就餐问题
有五位哲学家,围坐在圆桌旁。
- 他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。
- 吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。
- 如果筷子被身边的人拿着,自己就得等待
筷子类
class Chopstick { String name; public Chopstick(String name) { this.name = name; } @Override public String toString() { return "筷子{" + name + '}'; } }
哲学家类
@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); } }
就餐
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("阿基米德", c5, c1).start(); } } //输出 //14:17:49.395 c.Philosopher [亚里士多德] - eating... //14:17:49.395 c.Philosopher [苏格拉底] - eating... //14:17:51.405 c.Philosopher [柏拉图] - eating... //14:17:53.415 c.Philosopher [柏拉图] - eating... //14:17:55.418 c.Philosopher [柏拉图] - eating... //14:17:57.424 c.Philosopher [柏拉图] - eating... 执行不多会,就执行不下去了
定位死锁
名称: 阿基米德 状态: cn.itcast.n4.deadlock.v1.Chopstick@4aaba93e上的BLOCKED, 拥有者: 苏格拉底 总阻止数: 1, 总等待数: 0 堆栈跟踪: cn.itcast.n4.deadlock.v1.Philosopher.run(TestDeadLock.java:41) - 已锁定 cn.itcast.n4.deadlock.v1.Chopstick@540277b5 名称: 苏格拉底 状态: cn.itcast.n4.deadlock.v1.Chopstick@7b0663b6上的BLOCKED, 拥有者: 柏拉图 总阻止数: 7, 总等待数: 2 堆栈跟踪: cn.itcast.n4.deadlock.v1.Philosopher.run(TestDeadLock.java:41) - 已锁定 cn.itcast.n4.deadlock.v1.Chopstick@4aaba93e 名称: 柏拉图 状态: cn.itcast.n4.deadlock.v1.Chopstick@11d11cbf上的BLOCKED, 拥有者: 亚里士多德 总阻止数: 2, 总等待数: 4 堆栈跟踪: cn.itcast.n4.deadlock.v1.Philosopher.run(TestDeadLock.java:41) - 已锁定 cn.itcast.n4.deadlock.v1.Chopstick@7b0663b6 名称: 亚里士多德 状态: cn.itcast.n4.deadlock.v1.Chopstick@60468775上的BLOCKED, 拥有者: 赫拉克利特 总阻止数: 7, 总等待数: 1 堆栈跟踪: cn.itcast.n4.deadlock.v1.Philosopher.run(TestDeadLock.java:41) - 已锁定 cn.itcast.n4.deadlock.v1.Chopstick@11d11cbf 名称: 赫拉克利特 状态: cn.itcast.n4.deadlock.v1.Chopstick@540277b5上的BLOCKED, 拥有者: 阿基米德 总阻止数: 2, 总等待数: 0 堆栈跟踪: cn.itcast.n4.deadlock.v1.Philosopher.run(TestDeadLock.java:41) - 已锁定 cn.itcast.n4.deadlock.v1.Chopstick@604687
苏格拉底 获得c1, 柏拉图 获得c2, 亚里士多德获得c3, 赫拉克利特 获得c4, 阿基米德获得c5
苏格拉底尝试获取c2 ,c2被柏拉图占有,因此阻塞, 柏拉图尝试获取c3 ,c3被亚历山多德占有,阻塞,同理,最后一个阿基米德尝试获取c1,也阻塞,出现死锁
使用顺序加锁的方式解决死锁问题
出现死锁的加锁顺序
线程1 获取锁顺序 A ,B
线程2 获取锁顺序B,A
顺序加锁方案
线程 1 获取锁顺序 A ,B
线程2 获取顺序 A,B
这样线程1获得A锁,线程2阻塞,不会持有B锁,线程可获取B锁,
活锁
活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,例如。
@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(); } }
饥饿
指那些优先级比较低的线程在阻塞等待临界区资源时,长时间得不到资源而不能执行的现象。
更换哲学家就餐的获取锁顺序
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(); } } //线程不再阻塞 //阿基米德获取锁的概率比较低,处于饥饿状态