Java学习笔记-多线程基础

15. 多线程基础

15.1 线程相关概念

  1. 程序(program)

    是为了完成特定任务,用某种语言编写的一组指令的集合,简单的说就是我们的代码

  2. 进程

    1. 进程是指运行中的程序,比如我们使用的QQ双击QQ.exe文件便启动了一个进程,操作系统会为该进程分配内存空间,又如我们写了一段代码,然后编译运行,也是启动了一个进程

    2. 进程是程序的一次执行,或是正在运行的一个程序,是动态过程:有它自身的产生,存在和消亡的过程

      在这里插入图片描述

  3. 线程

    1. 线程由进程创建,是进程的一个实体,如下载和上传,可以同时下载多个文件,这就产生了多个线程,又如QQ聊天时的多个窗口,你可以跟A同学聊天,同时又跟B同学聊天,也是线程

    2. 一个进程可以有多个线程,便有单线程和多线程的概念,如下图

      • 单线程:同一时刻,只能执行一个线程

      • 多线程:同一时刻,可以执行多个线程,比如一个QQ进程可以打开多个聊天窗口

      在这里插入图片描述

  4. 并发

    1. 同一时刻,多个任务交替执行,造成一种 “貌似同时” 的错觉,简单的说就是单核CPU实现的多任务就是并发

    2. 如下图所示,单核CPU,它要执行两个任务,因此它只能一会执行QQ,一会执行迅雷这样交替进行,就是并发,也可以比作人的大脑,边开车边打电话就是并发

      在这里插入图片描述

  5. 并行

    1. 同一时刻,多个任务同时执行,多核CPU可以实现并行,或者并发和并行

    2. 例如下图,对于双核CPU,如果再开一个进程,比如说微信,对于上面的CPU可能就是并发的(交替)执行QQ和微信,但是对于CPU执行QQ和迅雷来说就是并行的

      在这里插入图片描述

15.2 线程基本使用

15.2.1 创建线程的方式

在Java中创建线程的方式有两种

  1. 继承Thread类,重写run方法(Thread意思即线程)

    class Dog extends Thread {
        @Override
        public void run() {
            //super.run();  一般不调用父类的
        }
    }
    
  2. 实现Runnable接口,重写run方法,其实Thread类也是实现了Runnable接口

    class Dog implements Runnable {
        @Override
        public void run() {   
        }
    }
    

    在这里插入图片描述

15.2.2 继承Thread类

在这里插入图片描述

步骤(以下面的代码为基础理解):

  1. 写一个类继承Thread类,并重写run方法

  2. 创建一个该类的对象,此时便可以当做线程来使用

  3. cat.start方法用来启动线程,内部代码会调用start0()这个方法,然后再在start0()这个方法中以特殊的方式调用run方法,若直接调用 run 只是普通的方法调用而已

    • start0() 是本地方法,是JVM调用, 底层是c/c++实现

      private native void start0();
      
    • 真正实现多线程的效果, 是start0(), 而不是run

    在这里插入图片描述

说明

  1. 当一个类继承了 Thread 类, 该类就可以当做线程使用

  2. 我们会重写 run方法,写上自己的业务代码

  3. run Thread 类 实现了 Runnable 接口的run方法

  4. 当主线程结束了,子线程不会结束,会继续执行,且子线程中可以再创建线程

阅读代码,注释及执行结果理解线程的使用:

public class Main {
    public static void main(String[] args) throws InterruptedException {
        // 创建Cat对象,可以当做线程使用
        Cat cat = new Cat();
        cat.start();   // 启动线程 -> 最终会执行cat的run方法

        // 理解线程:执行cat.start之后,开启另一个线程,原本只有一个线程
        // 即main函数这里这个主线程,开启线程后,下面的代码将会和run里的代码
        // 会交替执行,即主线程和子线程交替执行
        
        // cat.run();//run方法就是一个普通的方法,如果只是这样普通调用run方法
        // 没有真正的启动一个线程,就会把run方法执行完毕才向下执行,因此不直接调用run方法
        // 说明: 当main线程启动一个子线程 Thread-0, 主线程不会阻塞, 会继续执行
        // 这时 主线程和子线程是交替执行..
        for(int i = 1; i<= 5; i++) {
            // Thread.currentThread().getName() 获取当前线程的名字
            Thread.sleep(100);   //休眠100ms
            System.out.println(Thread.currentThread().getName() + "主线程" + i);
        }
    }
}

class Cat extends Thread {
    int count = 0;

    @Override
    public void run() {
        while(true) {

            if(count == 5) break;

            System.out.println(Thread.currentThread().getName() +
                    "子线程:小猫喵喵叫..." + (++count));

            try {
                Thread.sleep(100);   //线程休眠100ms,1000ms=1s
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

以上代码执行结果:

在这里插入图片描述

在这里插入图片描述

15.2.3 实现Runnable接口

  1. 基本介绍

    1. java 是单继承机制,在某些的情况下一个类可能已经继承了另一个类,这时便不能在继承Thread类了,用继承Thread的方法创建线程就不可能了
    2. javad设计者便提供了另一种方式创建线程,就是通过实现Runnable接口来实现线程
  2. 举例讲解

    在这里插入图片描述

    1. 写法一:先创建一个dog,在new一个Thread,然后在调用Threadstart函数

      //这里用到了静态代理模式
      public class Thread02 {
          public static void main(String[] args) {
              Dog dog = new Dog();
              //dog.start(); 这里不能调用start
              //创建了Thread对象,把 dog对象(实现Runnable),放入Thread
              Thread thread = new Thread(dog);
              thread.start();
              
          }
      }
      
      class Dog implements Runnable { //通过实现Runnable接口,开发线程
          int count = 0;
          @Override
          public void run() { //普通方法
              //写自己的业务逻辑代码
          }
      }
      
    2. 方法二:直接在类中维护一个私有的Thread对象,然后写一个start(注意这不是重写),在方法中初始化该Thread对象,然后调用它的start方法

      public class test {
          public static void main(String[] args) {
              Dog dog = new Dog();
              dog.start();
              for(int i=1;i<1000;i++) {
                  System.out.println("000");
              }
              // Thread类的start方法只能调用一次,如果下面的t.start放外面了的话
              // 那这里第二次调用dog.start就会报错
      //        dog.start();  
          }
      }
      
      class Dog implements Runnable {
          private Thread t;   //维护一个私有的Thread对象
      
          @Override
          public void run() {
              for(int i=0;i<10;i++) {
                  System.out.println("1");
              }
          }
      
          public void start() {
              if(t==null) {
                  t = new Thread(this);
                  t.start();   //注意这个是放里面,而不是外面,保证start方法只被调用一次
              }
          }
      }
      
      

    静态代理设计模式的简单理解:

    public class Thread02 {
        public static void main(String[] args) {
            Tiger tiger = new Tiger();//实现了 Runnable
            ThreadProxy threadProxy = new ThreadProxy(tiger);
            threadProxy.start();
        }
    }
    
    //线程代理类 , 模拟了一个极简的Thread类
    class ThreadProxy implements Runnable {//你可以把Proxy类当做 ThreadProxy
    
        private Runnable target = null;//属性,类型是 Runnable
    
        @Override
        public void run() {
            if (target != null) {
                target.run();//动态绑定(运行类型Tiger)
            }
        }
    
        public ThreadProxy(Runnable target) {
            this.target = target;
        }
    
        public void start() {
            start0();//这个方法时真正实现多线程方法
        }
    
        public void start0() {
            run();
        }
    }
    

15.2.4 JConsole监控线程

15.2.2继承Thread类中的代码为例

首先将循环结束的条件改大点或休眠时间长点,避免还没打开JConsole线程已经执行完了

步骤

  1. 开始运行程序后,点击Terminal(终端)

    在这里插入图片描述

  2. 输入JConsole(不分大小写)回车,便会进入Java监视和管理控制台窗口,然后再窗口中选择本地进程,便会在里面看见当前进程即com.qingtian.demo1.Main(包名+类名),选择后点击连接即可

    在这里插入图片描述

  3. 连接中会出现这个情况,直接不安全连接即可

    在这里插入图片描述

  4. 之后在左上角选择线程,然后在左下角可以找到main和Thread-0,即两个线程的名字

    在这里插入图片描述

    在这里插入图片描述

  5. 当main线程执行结束时,此时便只剩下Thread-0在执行

    在这里插入图片描述

    在这里插入图片描述

  6. 最后Thread-0也执行完,之后便失去连接,并且连不上了

    在这里插入图片描述

15.2.5 Thread 与 Runnable

  1. 多线程的理解

    在这里插入图片描述

  2. 继承 Thread vs 实现 Runnable 的区别

    在这里插入图片描述

  3. 两者面临的问题:线程同步问题

    在这里插入图片描述

    public class SellTicket {
        public static void main(String[] args) {
    
            //测试
    //        SellTicket01 sellTicket01 = new SellTicket01();
    //        SellTicket01 sellTicket02 = new SellTicket01();
    //        SellTicket01 sellTicket03 = new SellTicket01();
    //
    //        //这里我们会出现超卖..
    //        sellTicket01.start();//启动售票线程
    //        sellTicket02.start();//启动售票线程
    //        sellTicket03.start();//启动售票线程
            
    //        //这里也会出现超卖..
            System.out.println("===使用实现接口方式来售票=====");
            SellTicket02 sellTicket02 = new SellTicket02();
    
            new Thread(sellTicket02).start();//第1个线程-窗口
            new Thread(sellTicket02).start();//第2个线程-窗口
            new Thread(sellTicket02).start();//第3个线程-窗口
    
    
        }
    }
    
    
    //使用Thread方式
    class SellTicket01 extends Thread {
    
        private static int ticketNum = 100;//让多个线程共享 ticketNum
    
        @Override
        public void run() {
            while (true) {
    
                if (ticketNum <= 0) {
                    System.out.println("售票结束...");
                    break;
                }
                //休眠50毫秒, 模拟
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                        + " 剩余票数=" + (--ticketNum));
            }
        }
    }
    
    //实现接口方式
    class SellTicket02 implements Runnable {
        private int ticketNum = 100;//让多个线程共享 ticketNum
    
        @Override
        public void run() {
            while (true) {
    
                if (ticketNum <= 0) {
                    System.out.println("售票结束...");
                    break;
                }
                //休眠50毫秒, 模拟
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                        + " 剩余票数=" + (--ticketNum));//1 - 0 - -1  - -2
            }
        }
    }
    

15.2.6 线程的终止

  1. 基本说明

    1. 当线程完成任务后,会自动退出
    2. 还可以通过使用变量来控制run方法退出的方式来停止线程,即通知方式
  2. 举例说明

    public class ThreadExit_ {
        public static void main(String[] args) throws InterruptedException {
            T t1 = new T();
            t1.start();
    
            //如果希望main线程去控制t1 线程的终止, 必须可以修改 loop
            //让t1 退出run方法,从而终止 t1线程 -> 通知方式
    
            //让主线程休眠 10 秒,再通知 t1线程退出
            System.out.println("main线程休眠10s...");
            Thread.sleep(10 * 1000);
            t1.setLoop(false);
        }
    }
    
    class T extends Thread {
        private int count = 0;
        //设置一个控制变量
        private boolean loop = true;
        @Override
        public void run() {
            while (loop) {
    
                try {
                    Thread.sleep(50);// 让当前线程休眠50ms
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("T 运行中...." + (++count));
            }
    
        }
    
        public void setLoop(boolean loop) {
            this.loop = loop;
        }
    }
    

15.3 线程常用方法

15.3.1 常用方法第一组

  1. 基本介绍

    方法作用
    setName设置线程名称
    getName返回该线程名称
    start使该线程开始执行,java虚拟机底层调用该线程的start0方法
    run调用线程对象的run方法
    setPriority更改线程的优先级,优先级有三个常量,可以自己看源码
    getPriority获取线程的优先级
    sleep在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
    interrupt中断休眠,提前结束sleep休眠,注意不是终止线程
  2. 注意细节

    1. start底层会创建新的线程,调用runrun就是一个简单的方法调用,不会启动新的线程

    2. 线程优先级的范围

      在这里插入图片描述

    3. interrupt结束线程的休眠,相对于唤醒

    4. sleep:线程的静态方法,使当前线程休眠

  3. 举例说明

    public class ThreadMethod01 {
        public static void main(String[] args) throws InterruptedException {
            //测试相关的方法
            T t = new T();
            t.setName("老韩");
            t.setPriority(Thread.MIN_PRIORITY);//1
            t.start();//启动子线程
    
            //主线程打印5 hi ,然后我就中断 子线程的休眠
            for(int i = 0; i < 5; i++) {
                Thread.sleep(1000);
                System.out.println("hi " + i);
            }
            System.out.println(t.getName() + " 线程的优先级 =" + t.getPriority());//1
            t.interrupt();//当执行到这里,就会中断 t线程的休眠.
        }
    }
    
    class T extends Thread { //自定义的线程类
        @Override
        public void run() {
            while (true) {
                for (int i = 0; i < 100; i++) {
                    //Thread.currentThread().getName() 获取当前线程的名称
                    System.out.println(Thread.currentThread().getName() + "吃包子~~~~" + i);
                }
                try {
                    System.out.println(Thread.currentThread().getName() + "休眠中~~~");
                    // 这里会休眠20秒,但是被main里面的interrupt,便抛出一个异常,之后便执行catch
                    Thread.sleep(20000);//20秒
                } catch (InterruptedException e) {
                    //当该线程执行到一个interrupt 方法时,就会catch 一个 异常, 可以加入自己的业务代码
                    //InterruptedException 是捕获到一个中断异常.
                    System.out.println(Thread.currentThread().getName() + "被 interrupt了");
                }
            }
        }
    }
    

15.3.2 常用方法第二组

  1. 基本介绍

    1. yield:线程的礼让,让出cpu,让其线程执行,但礼让的时间的不确定,所以也不一定礼让成功

      在这里插入图片描述

    2. join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有任务,在执行被插队的线程,注意join 方法是在其他线程中调用,如线程t1调用线程t2的join的方法,意思就是先把t1线程占用的CPU让给t2先执行完,再执行t1

      在这里插入图片描述

      在这里插入图片描述

      public class ThreadMethod02 {
          public static void main(String[] args) throws InterruptedException {
      
              T2 t2 = new T2();
              t2.start();
      
              for(int i = 1; i <= 20; i++) {
                  Thread.sleep(1000);
                  System.out.println("主线程(小弟) 吃了 " + i  + " 包子");
                  if(i == 5) {
                      System.out.println("主线程(小弟) 让 子线程(老大) 先吃");
                      //join, 线程插队,插队成功后先执行t2的线程在执行这里的
                      //t2.join();// 这里相当于让t2 线程先执行完毕
                      Thread.yield();//礼让,不一定成功..
                      System.out.println("线程(老大) 吃完了 主线程(小弟) 接着吃..");
                  }
              }
          }
      }
      
      class T2 extends Thread {
          @Override
          public void run() {
              for (int i = 1; i <= 20; i++) {
                  try {
                      Thread.sleep(1000);//休眠1秒
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  System.out.println("子线程(老大) 吃了 " + i +  " 包子");
              }
          }
      }
      

15.3.3 用户线程和守护线程

  1. 基本介绍

    1. 用户线程:也叫工作线程,当线程的任务执行完通知方式介绍

    2. 守护线程:一般是为工作线程服务,当所有的用户线程结束,守护线程自动结束

      常见的守护线程:垃圾回收机制

  2. 举例讲解

    public class ThreadMethod03 {
        public static void main(String[] args) throws InterruptedException {
            MyDaemonThread myDaemonThread = new MyDaemonThread();
            //如果我们希望当main线程结束后,子线程自动结束
            //,只需将子线程设为守护线程即可
            myDaemonThread.setDaemon(true);  //将这个线程设置成守护线程
            myDaemonThread.start();
    
            //main线程,设置守护线程后,若下面的执行玩了,那子线程即无限for循环也会结束
            for( int i = 1; i <= 10; i++) {
                System.out.println("宝强在辛苦的工作...");
                Thread.sleep(1000);
            }
        }
    }
    
    class MyDaemonThread extends Thread {
        public void run() {
            for (; ; ) {//无限循环
                try {
                    Thread.sleep(1000);//休眠1000毫秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("马蓉和宋喆快乐聊天,哈哈哈~~~");
            }
        }
    }
    

15.4 线程的生命周期

  1. 基本介绍

    在这里插入图片描述

  2. 线程状态转换图

    在这里插入图片描述

    解释说明:

    1. 新new的一个对象,还没有调用start方法前处于New状态
    2. 调用的start的线程处于Runnable状态,其中Runnable状态又可以细分为Ready(准备状态)和Running状态(运行状态)
    3. Runnable状态的线程调用Thread.sleep()或者其他方法则该线程将会进入TimeWaiting状态
    4. Runnable状态的线程调用自身的wait()方法或者在自身线程里调用其他线程的join()方法,就会进入Waiting状态
    5. 等待进入同步代码块,即等待对象锁时,就会处于Blocked状态(堵塞状态)
    6. 线程结束后,便进入了Terminated状态
  3. 举例查看线程状态图

    public class ThreadState_ {
        public static void main(String[] args) throws InterruptedException {
            T t = new T();
            System.out.println(t.getName() + " 状态 " + t.getState());
            t.start();
    
            while (Thread.State.TERMINATED != t.getState()) {
                System.out.println(t.getName() + " 状态 " + t.getState());
                Thread.sleep(500);
            }
    
            System.out.println(t.getName() + " 状态 " + t.getState());
        }
    }	
    
    class T extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 2; i++) {
                System.out.println("hi " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    在这里插入图片描述

15.5 线程的同步

  1. 基本介绍

    解决15.2.5中多卖出了票的问题

    1. 在多线程编程中,一些敏感数据不允许被多个线程同时访问,此时就需要使用同步访问技术保证数据在任何一时刻,最多有一个线程访问,以保证数据的完整性

    2. 理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作

  2. 同步具体方法-Synchronized

    1. 同步代码块

      synchronized(对象) {  //得到对象锁,才能操作同步代码
          需要被同步的代码
      }
      
    2. synchronized还可以放在方法声明中,表示整个方法为同步方法

      public synchronized void m(String name) {
          需要被同步的代码
      }
      

      在这里插入图片描述

  3. 分析同步原理

    在这里插入图片描述

15.6 互斥锁

  1. 基本介绍

    1. Java 语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
    2. 每个对象都对应与一个可称为 “互斥锁” 的标记,这个标记用来保证在任一时刻,只能一个线程访问该对象
    3. 关键字synchronized来与对象的互斥锁联系,当某个对象用synchronized修饰时,表明该对象在任意时刻只能由一个线程访问
    4. 同步的局限性:导致程序的执行效率降低
    5. 同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象
    6. 同步方法(静态的)的锁为当前类本身
  2. 举例讲解

    使用互斥锁来解决售票问题

    public class SellTicket {
        public static void main(String[] args) {
    
            //测试一把,都是用同一对象创建的三个线程,因此下面的对象锁object也是同一个
            SellTicket03 sellTicket03 = new SellTicket03();
            new Thread(sellTicket03).start();//第1个线程-窗口
            new Thread(sellTicket03).start();//第2个线程-窗口
            new Thread(sellTicket03).start();//第3个线程-窗口
    
        }
    }
    
    
    //实现接口方式, 使用synchronized实现线程同步
    class SellTicket03 implements Runnable {
        private int ticketNum = 100;//让多个线程共享 ticketNum
        private boolean loop = true;//控制run方法变量
        Object object = new Object();
    
    
        //同步方法(静态的)的锁为当前类本身
        //老韩解读
        //1. public synchronized static void m1() {} 锁是加在 SellTicket03.class
        //2. 如果在静态方法中,实现一个同步代码块.
        /*
            synchronized (SellTicket03.class) {
                System.out.println("m2");
            }
         */
        public synchronized static void m1() {
    
        }
        public static  void m2() {
            synchronized (SellTicket03.class) {
                System.out.println("m2");
            }
        }
    
        //老韩说明
        //1. public synchronized void sell() {} 就是一个同步方法
        //2. 这时锁在 this对象
        //3. 也可以在代码块上写 synchronize ,同步代码块, 互斥锁还是在this对象
        public /*synchronized*/ void sell() { //同步方法, 在同一时刻, 只能有一个线程来执行sell方法
            
            // 因为是同一个对象,所有成员变量object也是同一个对象
            synchronized (/*this*/ object) {
                if (ticketNum <= 0) {
                    System.out.println("售票结束...");
                    loop = false;
                    return;
                }
    
                //休眠50毫秒, 模拟
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                        + " 剩余票数=" + (--ticketNum));//1 - 0 - -1  - -2
            }
        }
    
        @Override
        public void run() {
            while (loop) {
    
                sell();//sell方法是一共同步方法
            }
        }
    }
    
  3. 注意事项

    1. 同步方法如果没有使用static修饰:默认锁对象为this
    2. 如果方法使用static修饰,默认锁对象:当前类.class
    3. 实现的步骤:
      • 需要先分析上锁的代码
      • 选择同步代码块或同步方法
      • 要求多个线程锁对象为同一个即可

15.7 线程的死锁

  1. 基本介绍

    多个线程都占用了对方的锁资源,但是不肯相让,导致了死锁,在编程是一定要避免死锁发生的

    在这里插入图片描述

  2. 举例讲解

    public class DeadLock_ {
        public static void main(String[] args) {
            //模拟死锁现象
            DeadLockDemo A = new DeadLockDemo(true);
            A.setName("A线程");
            DeadLockDemo B = new DeadLockDemo(false);
            B.setName("B线程");
            A.start();
            B.start();
        }
    }
    
    
    //线程
    class DeadLockDemo extends Thread {
        static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用static
        static Object o2 = new Object();
        boolean flag;
    
        public DeadLockDemo(boolean flag) {//构造器
            this.flag = flag;
        }
    
        @Override
        public void run() {
    
            //下面业务逻辑的分析
            //1. 如果flag 为 T, 线程A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
            //2. 如果线程A 得不到 o2 对象锁,就会Blocked
            //3. 如果flag 为 F, 线程B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
            //4. 如果线程B 得不到 o1 对象锁,就会Blocked
            if (flag) {
                synchronized (o1) {//对象互斥锁, 下面就是同步代码
                    System.out.println(Thread.currentThread().getName() + " 进入1");
                    synchronized (o2) { // 这里获得li对象的监视权
                        System.out.println(Thread.currentThread().getName() + " 进入2");
                    }
                    
                }
            } else {
                synchronized (o2) {
                    System.out.println(Thread.currentThread().getName() + " 进入3");
                    synchronized (o1) { // 这里获得li对象的监视权
                        System.out.println(Thread.currentThread().getName() + " 进入4");
                    }
                }
            }
        }
    }
    

15.8 释放锁

  1. 下面操作会释放锁

    1. 当前线程的同步方法,同步代码块执行结束

      类比:上厕所,完事出来

    2. 当前线程在同步代码块,同步方法中遇到breakreturn

      类比:没有正常完事,经理叫他修改bug,不得已出来

    3. 当前线程在同步代码块,同步方法中出现了未处理的ErrorException,导致异常结束

      类比:没有正常完事,发现忘带纸,不得已出来

    4. 当前线程在同步代码块,同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁

      类比:没有正常完事,觉得需要酝酿,所以出来等会再进去

  2. 下面操作不会释放锁

    1. 线程执行同步代码块或同步方法时,程序调用Thread.sleep()Thread.yield()方法暂停当前线程的执行,不会释放锁

      类比:上厕所,太困了,在坑位上眯了会

    2. 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁

      注意:应尽量避免使用suspend()resume()来控制线程,方法不在推荐使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

catcfm

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值