深入了解Java多线程的基础知识!

前言

本文主要是为了巩固一下基础知识,对多线程的锁,wait,notify,中断做一个比较全面的总结,方便以后学习参考!同时在写博客的时候,查阅了资料,也做到一个查漏补缺的作用吧!希望可以和大家一起查漏补缺!

同步

简介

  1. Java 提供了多种线程之间通信的机制,其中最基本的就是使用同步 (synchronization)
  2. 其使用监视器 (monitor) 来实现。java中的每个对象都关联了一个监视器,线程可以对其进行加锁和解锁操作。
  3. 在同一时间,只有一个线程可以拿到对象上的监视器锁。如果其他线程在锁被占用期间试图去获取锁,那么将会被阻塞直到成功获取到锁。同时,监视器锁可以重入,也就是说如果线程 t 拿到了锁,那么线程 t 可以在解锁之前重复获取锁,每次解锁操作会反转一次加锁产生的效果。

关于同步,大家可以了解一下Java并发编程中Synchronized这个关键字,如果有不了解的,可以先阅读下<深入了解Synchronized原理>,这篇文章说得比较详细,对于阅读下文有很大的帮助!

TIPS:对Class对象加锁和对实例对象加锁,它们之间不构成同步。对于一个类中的两个不同的static方法加锁之间是构成同步的!

等待集合和唤醒

  1. 我们知道,每个Java对象都与一个监视器关联,同时也关联了一个等待集合,也就是存放想要获取这个锁的线程数据结构

  2. 当一个对象被创建出来时,它的等待集合是空的,对于向等待集合中添加或者移除线程的操作都是原子的,以下几个操作可以操纵这个等待集合:Object.wait, Object.notify, Object.notifyAll。

概览

  1. Threadsleep,join,interrupted
  2. 继承自Object的wait,notify,notifyAll
  3. Java的中断(重要)

wait

说明

等待一般由这几个方法触发,wait()wait(long millisecs)wait(long millisecs, int nanosecs),我们着重讲下wait

如果调用 wait 方法时没有抛出InterruptedException 异常,则表示正常返回。

假设有线程t,对象object,object的对象头的设置了线程的ID为n,目前还没解锁,现在执行object.wait(),会有以下几种情况发生

  1. 假如object的对象头没有设置线程t的ID,那么说明该线程并没有拥有该锁,如果用wait会抛出IllegalMonitorStateException的异常。
  2. 如果线程t被中断,那么中断标志被设置为true,并且抛出InterruptedException的异常,并且会重新设置中断标志位false
  3. 假如1和2都没有发生,那么说明没有抛出异常,之前就已经拿到了锁。那么说明调用wait方法正常,就正常挂起然后释放对象锁
分析
  1. 假如线程 t 在调用wait方法之后(我们假设是正常的情况),那么该线程t就会进入object对象的waitSet中,并且释放该对象对应的锁,下面这段代码表示正常的获取锁,解锁操作!

     public Object object = new Object();
     void thread1() {
         synchronized (object) { // 1. 获取监视器锁
             try {
                 object.wait(); // 2. 解锁
             } catch (InterruptedException e) {
                 // do somethings
             }
         }
     }
    
  2. 此时线程t不会有任何操作啦,直到从objectwaitSet中出来,这里的出来,有以下几种情况

    • object调用了notify方法,并且线程 t 在waitSet中被选中然后移出
    • object调用了notifyAll方法,并且线程 t 在waitSet中被移出
    • 线程 t 被 Interrupt
    • 如果线程 t 是调用 wait(millisecs)或者 wait(millisecs, nanosecs) 方法进入waitSet的,那么过了millisecs 毫秒或者 (millisecs*1000000+nanosecs) 纳秒后,线程 t 也会从waitSet中移出。

    这里线程t从waitSet中出来,并不是马上就往下执行,需要获取锁才行,那么怎么才能获取锁呢?在Synchronized中只有另外一个线程释放掉锁才行!这是需要注意的!

  3. 线程 t 正常被notify/notifyAll,获取锁,然后继续从之前wait的地方往下执行啦!

  4. 线程 t 在wait的时候被中断从而导致从waitSet中出来,那么线程 t 的中断状态会被重新设置为false,并且抛出InterruptedException的异常

notify

说明

顾名思义,就是通知 在 对象object的waitSet的线程 t 出来呗!让线程 t 自己去重新获取锁,然后执行它自己接下来的操作。

分析

在其他线程调用了object的notify/notifyAll。可能会发生下面某一种情况

  1. notify操作,那么object的waitSet不为空,那么就让在waitSet的某一个线程出来。假如说 线程t 在object的waitSet,其他线程调用object的notify,移除的不一定是线程 t,对于哪个线程会被选中而被移出,虚拟机没有提供任何保证。这里需要注意的是,恢复之后的线程 t 如果对 object 进行加锁操作将不会成功,直到拥有锁的线程完全释放锁。
  2. notifyAll操作,那么等待集合中的所有线程都将从等待集合中移出,然后恢复,这些线程恢复后,只有一个线程可以锁住监视器。其他没有拿到锁的线程就继续进入object的waitSet呗。

Interruptions

说明

上面我们提到了中断,现在我们来说一下,

在线程 t 调用线程 u 上的方法u.interrupt(),这个操作会将 u 的中断状态设置为 true。

在u的中断状态设置为true之后,下面几个方法会感知到,也就是说只要u的中断状态设置为true,那么下面几个方法会做出反应

  1. wait(), wait(long), wait(long, int), join(), join(long), join(long, int), sleep(long), sleep(long, int)

    这些方法的方法签名上都有throws InterruptedException,这个就是用来响应中断状态修改的。

  2. 如果线程阻塞在 InterruptibleChannel 类的 IO 操作中,那么这个 channel 会被关闭。

  3. 如果线程阻塞在一个 Selector 中,那么 select 方法会立即返回。

如果是上面的情况中当u 的中断状态设置为 true之后(也就是调用u.interrupt()),会将中断状态重新设置为 false重新设置为 false重新设置为 false,然后执行相应的操作(通常就是跳到 catch 异常处)。

如果不是以上的情况,那么,线程的interrupt()方法被调用,会将线程的中断状态设置为 true。(注意是这里不同)

当然,除了这几个方法, LockSupport 中的park 方法也能自动感知到线程被中断,当然,它不会重置中断状态为 false。再说一遍,只有上面的几种情况会在感知到中断后先重置中断状态为 false,然后再继续执行。

这里我们再啰嗦一下,如果有一个对象 o,而且线程 t 此时在 o 的等待集合中,那么 t 将会从 o 的等待集合中移出。这会让 t 从 wait 操作中恢复过来,t 此时需要获取 o 的监视器锁,获取完锁以后,发现线程 t处于中断状态,此时会抛出 InterruptedException 异常。

实例方法 thread.isInterrupted() 可以知道线程的中断状态。

调用静态方法 Thread.interrupted()可以返回当前线程的中断状态,同时将中断状态设置为false。

这个方法调用两次,那么第二次一定会返回 false,因为第一次会重置状态。前提是两次调用的中间没有发生设置线程中断状态的其他语句。

等待,通知,中断的交互

说明

如果一个线程在wait期间,同时发生了通知和中断,它将发生:

  1. 线程被notify唤醒,从wait中正常返回,然后被中断,那么只是设置一下中断标志为true,后面也不会改变中断状态,又不会抛出异常
  2. 先被中断,再notify,那么中断这个操作已经把线程从对象锁的waitSet中取出了,所以notify就不会唤醒这个线程了,那么如之前所说的,这个被中断的线程的中断状态是先被设置为true,被wait感受到中断后,再重新设置为false,那么以后想查看这个线程的状态肯定是false啦!后面就是正常的抛出异常了!

考虑是否有这个场景:x 被设置了中断状态,notify 选中了集合中的线程 x,那么这次 notify 将唤醒线程 x,其他线程(我们假设还有其他线程在等待)不会有变化。

答案:存在这种场景。因为这种场景是满足上述条件的,而且此时 x 的中断状态是 true。

例子
  1. 我们先来验证下线程被从wait到被notify之后,需要重新获得锁才能从之前wait的地方往下走的

    package common;
     
     public class WaitNotify {
    
         public static void main(String[] args) {
         Object object = new Object();
             new Thread(new Runnable() {
                 @Override
                 public void run() {
                     synchronized (object){
                         System.out.println("t1获取object的锁");
                         try {
                             System.out.println("t1开始休眠啦!并且交出object的锁!");
                             object.wait();
                             System.out.println("t1过了很久终于恢复啦。为什么t2通知我还没恢复呢?因为t2通知我的时候没拿到锁呀!");
                         } catch (InterruptedException e) {
                             System.out.println("t1在wait的时候发生了异常啦!");
                             e.printStackTrace();
                         }
     
                     }
             }
             },"t1").start();
     
             try {
             Thread.sleep(1000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
     
             new Thread(new Runnable() {
             @Override
                 public void run() {
                     synchronized (object){
                         System.out.println("t2获取到t1释放的锁啦!");
                         System.out.println("t2 notify一下,但是被通知的t1不能拿到锁,因为我还没释放哈哈哈");
                         object.notify();
                         System.out.println("t2 notify完了,那我先sleep 1s 吧,但是和wait不一样,我sleep的时候是不会交出锁的!");
                         try {
                             Thread.sleep(1000);
                         } catch (InterruptedException e) {
     
                         }
                     System.out.println("t2休息够了,结束我的操作了!就让出锁吧");
                     }
                 }
             },"t2").start();
         }
     }
     
    

    运行结果

    t1获取object的锁
    t1开始休眠啦!并且交出object的锁!
    t2获取到t1释放的锁啦!
    t2 notify一下,但是被通知的t1不能拿到锁,因为我还没释放哈哈哈
    t2 notify完了,那我先sleep 1s 吧,但是和wait不一样,我sleep的时候是不会交出锁的!
    t2休息够了,结束我的操作了!就让出锁吧
    t1过了很久终于恢复啦。为什么t2通知我还没恢复呢?因为t2通知我的时候没拿到锁呀!

  2. 我们做点修改,让t1线程被中断,然后被需要重新获取锁才能继续往下走,也就是捕获异常

    package common;
    
    public class WaitNotify {
    
        public static void main(String[] args) {
            Object object = new Object();
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (object){
                        System.out.println("t1获取object的锁");
                        try {
                            System.out.println("t1开始休眠啦!并且交出object的锁!");
                            object.wait();
                            System.out.println("t1过了很久终于恢复啦。为什么t2通知我还没恢复呢?因为t2通知我的时候没拿到锁呀!");
                        } catch (InterruptedException e) {
                            System.out.println("t1在wait过了很久终于发生了异常啦!因为t2开始中断的时候我还没拿到锁,等到t2释放锁我才到这里!");
    //                        e.printStackTrace();
                        }
    
                    }
                }
            },"t1");
            t1.start();
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (object){
                        System.out.println("t2获取到t1释放的锁啦!");
                        System.out.println("t2 notify一下,但是被通知的t1不能拿到锁,因为我还没释放哈哈哈");
                        t1.interrupt();
                        System.out.println("t2 notify完了,那我先sleep 1s 吧,但是和wait不一样,我sleep的时候是不会交出锁的!");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
    
                        }
                        System.out.println("t2休息够了,结束我的操作了!就让出锁吧");
                    }
                }
            },"t2");
            t2.start();
        }
    }
    
    

    运行结果

    t1获取object的锁
    t1开始休眠啦!并且交出object的锁!
    t2获取到t1释放的锁啦!
    t2 notify一下,但是被通知的t1不能拿到锁,因为我还没释放哈哈哈
    t2 notify完了,那我先sleep 1s 吧,但是和wait不一样,我sleep的时候是不会交出锁的!
    t2休息够了,结束我的操作了!就让出锁吧
    t1在wait过了很久终于发生了异常啦!因为t2开始中断的时候我还没拿到锁,等到t2释放锁我才到这里!

  3. 中断和notify同时发生的情况,如果是先被中断,再notify,那么就如之前所说的啦,直接看代码!

    package common;
    
    public class WaitNotify {
    
        volatile int a = 0;
    
        public static void main(String[] args) {
    
            Object object = new Object();
            WaitNotify waitNotify = new WaitNotify();
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (object){
                        System.out.println("t1获取object的锁");
                        try {
                            System.out.println("t1开始wait啦!并且交出object的锁!");
                            object.wait();
                            System.out.println("t1在被t3 notify 之后拿到锁才执行这条语句");
                        } catch (InterruptedException e) {
                            //先被中断,由于t1已经从waitSet中出来了,所以notify就不会有t1了!
                            System.out.println("t1在wait的时候被中断啦!虽然被中断了,但是t1只能在获取锁之后才可以执行这条语句,由于t1已经从waitSet中出来了,所以notify就不会有t1了!" + Thread.currentThread().isInterrupted());
    //                        e.printStackTrace();
                        }finally {
                            if(Thread.interrupted()){ //t1先被notify然后再被中断的情况,此时Thread.interrupted()是为true的
                                System.out.println("t1在被中断之前就被notify啦,但是t2是不会被唤醒的!t1并且不抛出中断异常,只是简单的设置中断标志位true" +
                                        " 然后重置为 " + Thread.interrupted());
                            }
                        }
    
                    }
                }
            },"t1");
    
            t1.start();
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (object){
                        System.out.println("t2获取到t1释放的锁啦!");
    
                        try {
                            System.out.println("t2开始wait啦!并且交出object的锁!");
                            object.wait();
                        } catch (InterruptedException e) {
    
                        }
                        System.out.println("t2重新拿到锁啦");
                    }
                }
            },"t2");
            t2.start();
    
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            Thread t3 = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (object){
                        System.out.println("t3获取到t2释放的锁啦!");
                        System.out.println("t3开始中断t1");
                        t1.interrupt();//这里测试是先中断,然后再notify的!
                        waitNotify.a = 1;//volatile 属性赋值主要是为了防止指令重排序
                        System.out.println("t3 执行notify,但是不知道后面唤醒的是哪一个!");
                        object.notify();
                        System.out.println("t3 调用完notify后,休息一会");
                        try {
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("t3休息够啦!,结束同步代码块!");
                    }
                }
            },"t3");
            t3.start();
    
        }
    }
    
    

    运行结果

  4. 中断和notify同时发生的情况,如果是先被notify,再中断的情况,之前也说过啦,那么直接看代码!

    package common;
    
    public class WaitNotify {
    
        volatile int a = 0;
    
        public static void main(String[] args) {
    
            Object object = new Object();
            WaitNotify waitNotify = new WaitNotify();
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (object){
                        System.out.println("t1获取object的锁");
                        try {
                            System.out.println("t1开始wait啦!并且交出object的锁!");
                            object.wait();
                            System.out.println("t1在被t3 notify 之后拿到锁才执行这条语句");
                        } catch (InterruptedException e) {
                            //先被中断,由于t1已经从waitSet中出来了,所以notify就不会有t1了!
                            System.out.println("t1在wait的时候被中断啦!虽然被中断了,但是t1只能在获取锁之后才可以执行这条语句,由于t1已经从waitSet中出来了,所以notify就不会有t1了!" + Thread.currentThread().isInterrupted());
    //                        e.printStackTrace();
                        }finally {
                            if(Thread.interrupted()){ //t1先被notify然后再被中断的情况,此时Thread.interrupted()是为true的
                                System.out.println("t1在被中断之前就被notify啦,但是t2是不会被唤醒的!t1并且不抛出中断异常,只是简单的设置中断标志位true" +
                                        " 然后重置为 " + Thread.interrupted());
                            }
                        }
    
                    }
                }
            },"t1");
    
            t1.start();
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (object){
                        System.out.println("t2获取到t1释放的锁啦!");
    
                        try {
                            System.out.println("t2开始wait啦!并且交出object的锁!");
                            object.wait();
                        } catch (InterruptedException e) {
    
                        }
                        System.out.println("t2重新拿到锁啦");
                    }
                }
            },"t2");
            t2.start();
    
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            Thread t3 = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (object){
                        System.out.println("t3获取到t2释放的锁啦!");
                        System.out.println("t3开始中断t1");
                        object.notify(); //这里特意先把notify放在前面是为了测试先被t1 notify,然后t1 再被中断的情况
                        waitNotify.a = 1;//volatile 属性赋值主要是为了防止指令重排序
                        System.out.println("t3 执行notify,但是不知道后面唤醒的是哪一个!");
                        t1.interrupt(); // t1 中断
                        System.out.println("t3 调用完notify后,休息一会");
                        try {
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("t3休息够啦!,结束同步代码块!");
                    }
                }
            },"t3");
            t3.start();
    
        }
    }
    
    

    运行结果

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值