并发编程11

并发编程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
         */
    }
    
    

猜你喜欢

转载自blog.csdn.net/Markland_l/article/details/111056279