并发编程11
- t1释放锁之后,唤醒t2线程,t2线程从LockSupport.park()处唤醒,继续往下执行
- Thread.interrupted()并不是打断线程,只是清除打断标记
线程打断
- sleep、wait、join这种线程如果被打断则会直接清除打断标记 --> 什么是打断标记
- 1.怎么看打断标记? Thread.interrupted()
- 2.打断标记是标记自己这个线程是否被别人打断过了
例子1—sleep线程打断
-
package BingFaBianCheng.bingFaBianCheng11; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; @Slf4j(topic = "enjoy") public class Lock3 { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { try { TimeUnit.SECONDS.sleep(100); } catch(InterruptedException e) { e.printStackTrace(); } }, "t1"); t1.start(); // 主要是为了让线程t1 先执行 // TimeUnit.SECONDS.sleep(1); // 本来你这里做了对t1的打断操作 为什么sleep要清除 t1.interrupt(); // 注释掉main方法中sleep方法,返回true? // t1还没有执行,也没有sleep,只是做了一个打断标记 // 因为你打断的是一个正常的线程(非sleep) log.debug("t1的打断标记[{}]",t1.isInterrupted()); } }
-
输出true,如果不注释TimeUnit.SECONDS.sleep(1),输出false
例子2—正常线程打断
-
package BingFaBianCheng.bingFaBianCheng11; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; @Slf4j(topic = "enjoy") public class Lock3 { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { while(true) { log.debug("t1空转--------]"); } }, "t1"); t1.start(); // 主要是为了让线程t1 先执行 TimeUnit.SECONDS.sleep(1); // 本来你这里做了对t1的打断操作 为什么sleep要清除 t1.interrupt(); log.debug("t1的打断标记[{}]",t1.isInterrupted()); } }
-
打断的是一个正常线程,返回的状态是true
-
综合上面两个例子,为什么sleep的线程被打断之后会清除打断标记?
-
为什么sleep的线程打断之后会清除打断标记,正常线程调用interrupt()不能被打断,给你一个打断标记,自己根据标记去判断
正常线程打断后无自动响应
-
package BingFaBianCheng.bingFaBianCheng11; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; @Slf4j(topic = "enjoy") public class Lock3 { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { while(true) { if(Thread.currentThread().isInterrupted()){ // 可以做自定义操作,做一些内存回收、资源释放 System.out.println("-----------"); break; } // log.debug("t1空转--------"); } }, "t1"); t1.start(); // 主要是为了让线程t1 先执行 TimeUnit.SECONDS.sleep(1); // 本来你这里做了对t1的打断操作 为什么sleep要清除 t1.interrupt(); log.debug("t1的打断标记[{}]",t1.isInterrupted()); } }
-
标准线程不会对interrupt()方法做出相应,所以需要根据打断标识来进行判断
sleep线程打断后自动响应
-
package BingFaBianCheng.bingFaBianCheng11; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; @Slf4j(topic = "enjoy") public class Lock3 { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { while(true) { try{ TimeUnit.SECONDS.sleep(100); } catch(InterruptedException e){ e.printStackTrace(); } // log.debug("t1空转--------"); } }, "t1"); t1.start(); // 主要是为了让线程t1 先执行 TimeUnit.SECONDS.sleep(1); // 本来你这里做了对t1的打断操作 为什么sleep要清除 t1.interrupt(); log.debug("t1的打断标记[{}]",t1.isInterrupted()); } }
-
sleep线程interrupted方法已经自我相应了,被try-catch住InterruptedException这个异常,已经执行完了,所以可以清楚打断标记
ReentrantLock(独占锁)加锁、解锁过程
实际打断但是没有打断
- doug lea — lock方法,线程是不可打断的,实际是打断的,制造给用户的是一种错觉,
- 理论上parkAndCheckInterrupt()方法中,Thread.interrupted()清空用户打断标记,理论上已经清楚,是没有被打断的
- 现象上—parkAndCheckInterrupt()执行完返回,由于是死循环,也会一直执行,所以表现出来也是没有打断的
不可打断的lock方法
-
package BingFaBianCheng.bingFaBianCheng11; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; @Slf4j(topic = "enjoy") public class Lock3 { public static void main(String[] args) throws InterruptedException { ReentrantLock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { lock.lock(); try { log.debug("t1------"); } finally { //如果重入的次数和unlock的次数不同则不能完全释放锁 lock.unlock(); } }, "t1"); t1.start(); lock.lock(); log.debug("main------"); // t1在park // 调用t1.interrupt()方法会把t1唤醒,需要给用户制造假象是没有被唤醒的 // doug lea写的死循环非常经典,唤醒后死循环又再次阻塞,所以看起来还是没有唤醒, // 注意此处是lock方法 // 1.aqs作者的需求是如果t1是lock方法加锁的 // t1.interrupt(); TimeUnit.SECONDS.sleep(10000); lock.unlock(); } }
可打断的lock方法
-
package BingFaBianCheng.bingFaBianCheng11; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; @Slf4j(topic = "enjoy") public class Lock3 { public static void main(String[] args) throws InterruptedException { // Sync这个内部类相当于aqs ReentrantLock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { try{ lock.lockInterruptibly(); } catch(InterruptedException e){ log.debug("不获取锁了"); e.printStackTrace(); } try { log.debug("t1------"); } finally { //如果重入的次数和unlock的次数不同则不能完全释放锁 lock.unlock(); } }, "t1"); t1.start(); lock.lock(); log.debug("main------"); // t1在park // 调用t1.interrupt()方法会把t1唤醒, t1.interrupt(); TimeUnit.SECONDS.sleep(10000); lock.unlock(); } }
-
可以唤醒
解锁流程
- 1.t1持有锁t2在park,然后t1释放锁,唤醒t2
t1持有锁,t2在park
-
Node head -> { thread=null prev=null next=t2 waitstatus=-1} ^ | V Node tail -> { thread=t2 pre=head next=null waitstatus=0} exclusiveOwnerThread:t1 state:1
然后t1释放锁
-
Node head -> { thread=null prev=null next=t2 waitstatus=-1} ^ | V Node tail -> { thread=t2 pre=head next=null waitstatus=0} // 先执行tryRelease()方法,由于此处没有重入, // exclusiveOwnerThread变为null,state变为0 exclusiveOwnerThread:null state:0
唤醒t2
-
// 执行unparkSuccessor方法 // 将waitStatus改为0,因为我现在正在叫醒,防止出现多个线程叫我叫醒 // 从-1改为0,表明我再也没有责任叫醒别人了 { thread=t2 prev=null next=t2z waitstatus=0} Node head、Node tail -> { thread=null pre=head next=null waitstatus=0} // setHead(node) // head = node,head节点指针也指向t2节点 // node.thread = null,t2节点的thead置为null // node.prev = null t2节点指向旧head节点的链接打断 // p.next = null 旧head节点指向t2节点的链接打断 // 所以旧head节点就变成了没有和任何链表链接,变得没有引用,迟早会被回收 // LockSupport.unpark(s.thread)方法去唤醒t2 // 从parkAndCheckInterrupt()中LockSupport.park(this)此处醒来,继续往下执行 // 继续执行acquireQueued()方法中的死循环 // 执行tryAcquire()方法,此时加锁成功,state改为1,exclusiveOwnerThread改为t2 // exclusiveOwnerThread:t2 state:1
debug解锁流程
-
package BingFaBianCheng.bingFaBianCheng11; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; @Slf4j(topic = "enjoy") public class Lock3 { public static void main(String[] args) throws InterruptedException { ReentrantLock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { lock.lock(); try { log.debug("t1------"); } finally { lock.unlock(); } }, "t1"); t1.start(); lock.lock(); log.debug("main------") lock.unlock(); } }
-
此处模拟的是main线程先持有锁t1线程park,然后main线程释放锁,t1线程被唤醒并获取到锁
t1线程采用lockInterruptibly方法加锁
-
package BingFaBianCheng.bingFaBianCheng11; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; @Slf4j(topic = "enjoy") public class Lock3 { public static void main(String[] args) throws InterruptedException { ReentrantLock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { try{ lock.lockInterruptibly(); } catch(InterruptedException e){ // 此处你想干嘛干嘛 log.debug("不获取锁了"); e.printStackTrace(); } try { log.debug("t1------"); } finally { // 方法不用执行,被唤醒后就直接抛异常了 // 不会去再去拿锁 // lock.unlock(); } }, "t1"); t1.start(); lock.lock(); log.debug("main------") lock.unlock(); } }
-
lock.lockInterruptibly()方法加锁后,被main线程唤醒后,就直接抛异常了,不会去再去拿锁
重入锁的释放锁
-
lock.unlock()只会释放一次锁,如果是重入锁,要执行多次
-
package BingFaBianCheng.bingFaBianCheng11; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; @Slf4j(topic = "enjoy") public class Lock3 { public static void main(String[] args) throws InterruptedException { ReentrantLock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { lock.lock(); try { log.debug("t1------"); } finally { //如果重入的次数和unlock的次数不同则不能完全释放锁 lock.unlock(); } }, "t1"); t1.start(); lock.lock(); log.debug("main------"); lock.unlock(); } }
条件锁的唤醒流程
-
package BingFaBianCheng.bingFaBianCheng11; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * 主要是验证signal方法会转移node(线程)到主队列 * 先进先出 231 */ @Slf4j(topic = "enjoy") public class Lock1 { public static void main(String[] args) throws InterruptedException { ReentrantLock lock = new ReentrantLock(); //条件队列 Condition wait = lock.newCondition(); Thread t1 = new Thread(() -> { lock.lock(); try { log.debug("t1 去await"); wait.await(); log.debug("t1 醒來"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }, "t1"); t1.start(); // Condition中也维护了一个类似aqs中的阻塞队列 //t1 最先得到锁 然后去wait队列当中阻塞 释放了锁 //t4 启动 获取到锁 睡眠5s 没有释放锁 5s之后 把t1 唤醒 Thread t4 = new Thread(() -> { try { lock.lock(); log.debug("t4--------獲取所---"); TimeUnit.SECONDS.sleep(5); wait.signal(); log.debug("交響t1"); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } }, "t4"); t4.start(); TimeUnit.SECONDS.sleep(1); //拿锁失败 主队列(lock自身的aqs阻塞队列)当中阻塞 Thread t2 = new Thread(() -> { try { lock.lock(); log.debug("t2----拿到鎖-------"); } catch (Exception e) { e.printStackTrace(); } finally { log.debug("t2----釋放-------"); lock.unlock(); } }, "t2"); t2.start(); //顺序启动 顺序入队 TimeUnit.SECONDS.sleep(1); // 拿锁失败 主队列(lock自身的aqs阻塞队列)当中阻塞 Thread t3 = new Thread(() -> { try { lock.lock(); log.debug("t3----拿到鎖-------"); } catch (Exception e) { e.printStackTrace(); } finally { log.debug("t3----释放-------"); lock.unlock(); } }, "t3"); t3.start(); } }
-
打印的顺序(获取到锁的顺序)是线程2、线程3、线程1
t4拿到锁,t2,t3在aqs队列中阻塞
-
Node head -> { thread=null prev=null next=t2 waitstatus=-1} ^ | V { thread=t2 pre=head next=t3 waitstatus=-1} ^ | V Node tail -> { thread=t3 pre=t2 next=null waitstatus=0} exclusiveOwnerThread:t4 state:1
t1在单独的条件队列中(t1执行await()方法)
-
CondtionObject first、last -> { thread=t1 prev=null next=null waitstatus = -2} // 执行addConditionWaiter方法,在条件队列中初始化头尾节点,都指向t1 // 此时生成的t2的节点的waitstatus为-2,-2表明是需要条件锁的signal方法来唤醒的 // 同时t1释放掉锁
t4线程执行signal方法
-
Node head -> { thread=null prev=null next=t2 waitstatus=-1} ^ | V { thread=t2 pre=head next=t3 waitstatus=-1} ^ | V { thread=t3 pre=t2 next=t1 waitstatus=-1} ^ | V Node tail -> { thread=t1 pre=t3 next=null waitstatus=0} exclusiveOwnerThread:t4 state:1
-
t4执行signal方法,首先把t1的waitstatus改为0,再执行aqs的enq方法,把t1链接到t3之后,并且tail指向t1,t3指向t1,再将t3的waitstatus改为-1
读写锁加锁、解锁(很复杂)
读读并发
-
package BingFaBianCheng.bingFaBianCheng11; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @Slf4j(topic = "enjoy") public class Lock2 { //读写锁 static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); static Lock r = rwl.readLock(); static Lock w = rwl.writeLock(); public static void main(String[] args) throws InterruptedException { /** * t1 最先拿到写(W)锁 然后睡眠了5s * 之后才会叫醒别人 */ Thread t1 = new Thread(() -> { w.lock(); try { log.debug("t1 获取锁"); TimeUnit.SECONDS.sleep(5); log.debug("5s 之后"); } catch (InterruptedException e) { e.printStackTrace(); } finally { w.unlock(); } }, "t1"); t1.start(); TimeUnit.SECONDS.sleep(1); /** * t1在睡眠的过程中 t2不能拿到 读写互斥 * t2 一直阻塞 */ Thread t2 = new Thread(() -> { try { r.lock(); log.debug("t2----+-------"); TimeUnit.SECONDS.sleep(1); } catch (Exception e) { e.printStackTrace(); } finally { log.debug("t2-----jian-------"); r.unlock(); } }, "t2"); t2.start(); TimeUnit.SECONDS.sleep(1); /** * t1在睡眠的过程中 t3不能拿到 读写互斥 * t3 一直阻塞 * * 当t1释放锁之后 t3和t2 能同时拿到锁 * 读读并发 */ Thread t3 = new Thread(() -> { try { r.lock(); log.debug("t3----+-------"); TimeUnit.SECONDS.sleep(1); } catch (Exception e) { e.printStackTrace(); } finally { log.debug("t3----释放-------"); r.unlock(); } }, "t3"); t3.start(); /** * 拿写锁 * t1睡眠的时候 t4也页阻塞 * 顺序应该 t2 t3 t4 * t1释放后,t2、t3会执行,t4不会执行 */ Thread t4 = new Thread(() -> { try { w.lock(); log.debug("t4--------+---"); TimeUnit.SECONDS.sleep(10); log.debug("t4--------醒来---"); } catch (Exception e) { e.printStackTrace(); } finally { log.debug("t4--------jian---"); w.unlock(); } }, "t4"); t4.start(); /** * * t5 是读锁 * 他会不会和t2 t3 一起执行 * t1释放后,t2、t3会执行,t4不会执行,t5也不会执行 */ Thread t5 = new Thread(() -> { try { r.lock(); log.debug("t5--------+---"); } catch (Exception e) { e.printStackTrace(); } finally { log.debug("t5--------jian---"); r.unlock(); } }, "t5"); t5.start(); } }
-
{ w thread=t1} ^ | V { r thread=t2 ws=-1} <-> { shared thread=null prev=null next=null} // 比普通独占锁中的节点多了shared属性,下一个节点是shared继续唤醒,直到遇到exclusive ^ | V { r thread=t3 ws=-1} <-> { shared thread=null prev=null next=null} ^ | V { w thread=t4 ws=-1} <-> { exclusive thread=null prev=null next=null} ^ | V { r thread=t5}
-
读写锁中,头部指向t2,尾部指向t3,如果t2是读锁,t2被唤醒后,会去找下一个,发现下一个也是读锁,head就会指向t3,t2节点就出去了,aqs队列中第一个永远等于null,叫做前置节点,去判断要不要去唤醒下一个节点
读写锁为什么不能升级
-
package BingFaBianCheng.bingFaBianCheng11; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; @Slf4j(topic = "enjoy") public class Lock3 { public static void main(String[] args) throws InterruptedException { ReentrantLock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { lock.lock(); try { log.debug("t1------"); } finally { //如果重入的次数和unlock的次数不同则不能完全释放锁 lock.unlock(); } }, "t1"); t1.start(); lock.lock(); log.debug("main------"); lock.unlock(); } /** * 读写锁为什么不能升级? * * 可能出现死锁,内部不支持这么写,实际可以这么写 * readWriteLock.r// t1 t2 t3 t4 * todo...//t1 5 * readWriteLock.w // 拿不到 t1等t2和t3释放 t2等t1和t3释放 * todo...//t2 * readWriteLock.ur * readWriteLock.uw * * * 读写锁为什么可以降级? * * readWriteLock.w// t1拿到 t2 t3 t4 都没拿到 * todo...//t1 5 * readWriteLock.r// t1 t2 t3 t4 都拿不到 * // t1释放写锁后t1继续执行拿到读锁,t2 t3 t4都没拿到写锁,无法向下 // 执行,继续去拿读锁 // 也就不会出现上面那种互相等待对方释放锁的死锁现象 * todo...//t2 * readWriteLock.uw * readWriteLock.ur */ }