• Java高级编程之多线程


    一、程序、进程、线程的理解

    1. 程序:是为完成特定任务、用某种语言编写的一组指令的集合。即一段静态的代码,静态对象。

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

      • 如:运行中的QQ,运行中的MP3播放器
      • 程序是静态的,进程是动态的
      • 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
    3. 线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。

      • 若一个进程同一时间并行执行多个线程,就是支持多线程的
      • 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开
        销小
      • 一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患
    4. 附加:内存的结构
      在这里插入图片描述

    • 进程可以细化为多个线程
    • 每个线程,拥有自己独立的:栈、程序计数器。
    • 多个线程,共享同一个进程的结构:方法区、堆。

    二、并行于并发

    1. 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
    2. 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。

    三、多线程的2种创建方式

    1. 方式一:继承于Thread类

    1. 创建一个继承于Thread类的子类
    2. 重写Thread类的run() --> 将此线程执行的操作声明在run()方法中
    3. 创建Thread类的子类对象
    4. 通过此类的对象调用start()
    //1. 创建一个继承于Thread的类
    class MyThread extends Thread{
        //2. 重写Thread类的run()
        @Override
        public void run() {
            for (int i = 0 ;i <= 1000; i++){
                if(i % 2 == 0){
                    System.out.println(Thread.currentThread().getName() + ": " + i);
                }
            }
        }
    }
    
    
    public class ThreadTest {
        public static void main(String[] args) {
    //        3. 创建Thread类的子类对象
            MyThread t1 = new MyThread();
    //        4. 通过此类的对象调用start():①启动当前线程 ②调用当前线程的run()
            t1.start();
    
    //        问题一:我们不能通过直接调用run()的方式启动线程
    //        t1.run();
    
    //        问题二:在启动一个线程,编译1000内的偶数,不可以还让已经start()的线程去执行。会报IllegalThreadStateException的异常
    //        t1.start(); // 报IllegalThreadStateException异常
    //        我们需要重新创建一个线程对象
            MyThread t2 = new MyThread();
            t2.start();
    
    
    //        如下操作仍然是在main线程执行的
            for (int i = 0 ;i <= 1000; i++){
                if(i % 2 != 0){
                    System.out.println(Thread.currentThread().getName() + ": " + "*********main()**********");
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    2. 方式二:实现Runnable接口

    1. 创建一个实现了Runnable接口的类
    2. 实现类去实现Runnable中的抽象方法:run()
    3. 创建实现类的对象
    4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
    5. 通过Thread类的对象调用start()
    //1. 创建一个实现了Runnable接口的类
    class MThread implements Runnable{
    
    //    2. 实现类去实现Runnable中的抽象方法:run()
        @Override
        public void run() {
            for(int i = 0 ; i <= 100; i++){
                if(i % 2 == 0){
                    System.out.println(Thread.currentThread().getName() + ": " + i);
                }
            }
        }
    }
    
    public class ThreadTest1 {
        public static void main(String[] args) {
    //        3. 创建实现类的对象
            MThread mThread = new MThread();
    //        4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
            Thread t1 = new Thread(mThread);
    //        5. 通过Thread类的对象调用start()
            t1.setName("线程1");
            t1.start();
    
            Thread t2 = new Thread(mThread);
            t2.setName("线程2");
            t2.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    3. 两个问题

    1. 问题一:我们不能通过直接调用run()的方式启动线程
    2. 问题二:在启动一个线程,编译1000内的偶数,不可以还让已经start()的线程去执行。会报IllegalThreadStateException的异常。此时我们需要重新创建一个对象。
    //        问题一:我们不能通过直接调用run()的方式启动线程
    //        t1.run();
    
    //        问题二:在启动一个线程,编译1000内的偶数,不可以还让已经start()的线程去执行。会报IllegalThreadStateException的异常
    //        t1.start(); // 报IllegalThreadStateException异常
    //        我们需要重新创建一个线程对象
            MyThread t2 = new MyThread();
            t2.start();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    四、Thread常用的方法

    1. start():启动当前线程,调用当前线程的run()
    2. run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
    3. currentThread():静态方法,返回执行当前代码的线程
    4. getName():获取当前线程的名字
    5. setName():设置当前线程的名字
    6. yield():释放当前cpu的执行权
    7. join():在线程a中调用线程b的join(),此时线程a回进入阻塞状态,直到线程b完全执行以后,线程a才结束阻塞状态
    8. stop():已过时,当执行此方法时,强制结束当前线程
    9. sleep():让当前线程“睡眠”指定的mills(毫秒)。在指定的mills时间内,当前线程时阻塞状态
    10. isAlive():判断当前线程是否存活

    线程的优先级

    1. 线程的优先级:
      MAX_PRIORITY:10
      MIN_PRIORITY:1
      NORM_PRIORITY:5 --> 默认优先级

    2. 如何获取和设置当前线程的优先级
      getPriority():获取当前线程
      setPriority():设置当前线程

    说明:高优先级的线程要抢占低优先级线程的cpu执行权,但是只是从概率上讲,高优先级的线程高概率的情况下呗执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行

    class  HelloThread extends Thread{
        @Override
        public void run() {
            for(int i = 0; i <= 100; i++){
                if(i % 2 == 0){
    //                try {
    //                    sleep(10);
    //                } catch (InterruptedException e) {
    //                    throw new RuntimeException(e);
    //                }
    
                    System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getPriority() + ": " + i);
                }
    
    //            if(i % 20 == 0){
    //                yield();
    //            }
            }
        }
    
    //    设置线程名字,方法一:
        public HelloThread(String name){
            super(name);
        }
    }
    
    public class ThreadMethodTest {
        public static void main(String[] args) {
            HelloThread h1 = new HelloThread("Thread一");
    //        设置线程名字,方法二:
    //        h1.setName("线程一");
    
            h1.setPriority(Thread.MAX_PRIORITY);
            h1.start();
    
    //        给主线程命名
            Thread.currentThread().setName("主线程");
            for(int i = 0; i <= 100; i++){
                if(i % 2 != 0){
                    System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getPriority() + ": " + i);
                }
    
    //            if(i == 20){
    //                try {
    //                    h1.join();
    //                } catch (InterruptedException e) {
    //                    throw new RuntimeException(e);
    //                }
    //            }
            }
    
    //        System.out.println(h1.isAlive());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    五、线程的生命周期

    1. 线程的几种状态

    1. 新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
    2. 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
    3. 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能
    4. 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
    5. 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

    2. 线程状态转换图

    在这里插入图片描述

    六、线程的同步机制

    1. 线程安全问题的描述

    1. 问题:买票过程中,出现了重票、错票… --> 出现了线程的安全问题
    2. 问题出现的原因:当某个线程操作车票的过程中,尚操作完成时,其他线程参与进来,也操作车票
    3. 如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完成ticket时,线程才可以开始操作ticket。这种情况即使线程a出现阻塞,也不能改变。
    4. 在Java中,我们通过同步机制,来解决线程安全的问题。
    5. 同步的方式,解决了线程的安全问题。(好处)
      操作同时代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。(坏处)

    2. 解决线程安全的方式

    方式一:同步代码块
    synchronized(同步监视器){
          // 需要同步的代码
    }
    
    • 1
    • 2
    • 3

    说明:

    1. 操作共享数据的代码,即为需要被同步的代码(不能包含代码多了,也不能包含代码少了)
    2. 共享数据:多个数据共同操作的变量。比如:ticket就是共享数据。
    3. 同步监视器(俗称:锁):任何一个类的对象,都可以充当锁。
      要求:多个线程必须要共用一把锁
    4. 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。

    使用同步方法代码块解决实现Runnable接口的方式的线程安全问题:

    class Window2 implements Runnable{
        private int ticket = 100;
        Object obj = new Object();
    
        @Override
        public void run() {
            while(true) {
                synchronized (this) {
                    if (ticket > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                        System.out.println(Thread.currentThread().getName() + ": 买票,票号为:" + ticket);
                        ticket--;
                    } else {
                        break;
                    }
                }
            }
        }
    }
    public class WindowTest2 {
        public static void main(String[] args) {
            Window2 window2 = new Window2();
            Thread t1 = new Thread(window2);
            Thread t2 = new Thread(window2);
            Thread t3 = new Thread(window2);
    
            t1.setName("窗口1:");
            t2.setName("窗口2:");
            t3.setName("窗口3:");
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    使用同步方法代码块解决继承Thread类的方式的线程安全问题:

    class Window3 extends Thread{
        private static int ticket = 100;
        private static Object obj = new Object();
    
        @Override
        public void run() {
            synchronized (Window3.class){ //类也是个对象,且是唯一的(使用当前类充当同步监视器)
    //        synchronized (obj) {
                while (true) {
                    if (ticket > 0) {
                        try {
                            sleep(100);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
    
                        System.out.println(getName() + ": 买票,票号为:" + ticket);
                        ticket--;
                    } else {
                        break;
                    }
                }
            }
        }
    }
    
    
    public class WindowTest3 {
        public static void main(String[] args) {
            Window3 t1 = new Window3();
            Window3 t2 = new Window3();
            Window3 t3 = new Window3();
    
            t1.setName("窗口一");
            t2.setName("窗口二");
            t3.setName("窗口三");
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    说明: 在继承Thread类创建的的多线程发方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。

    方式二:同步方法
    1. 同步方法仍然涉及到同步监视器,只是不需要显式的声明。
    2. 非静态的同步方法,同步监视器是:this
      静态的同步方法,同步监视器是:当前类的本身

    使用同步方法解决实现Runnable接口的方式的线程安全问题:

    class Window4 implements Runnable{
        private int ticket = 100;
    
        @Override
        public void run() {
            while(true){
                if(ticket <= 0) break;
                show();
            }
        }
    
        private synchronized void show(){ //同步监视器:this
            if(ticket > 0){
    //            try {
    //                Thread.sleep(10);
    //            } catch (InterruptedException e) {
    //                throw new RuntimeException(e);
    //            }
                System.out.println(Thread.currentThread().getName() + ": 买票,票号为:" + ticket);
                ticket--;
            }
        }
    }
    
    public class WindowTest4 {
        public static void main(String[] args) {
            Window4 window4 = new Window4();
            Thread t1 = new Thread(window4);
            Thread t2 = new Thread(window4);
            Thread t3 = new Thread(window4);
    
            t1.setName("窗口一");
            t2.setName("窗口二");
            t3.setName("窗口三");
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    使用同步方法解决继承Thread类的方式的线程安全问题:

    class Window5 extends Thread {
        private static int ticket = 100;
    
        @Override
        public void run() {
            while (true) {
                if(ticket <= 0) break;
                show();
            }
        }
    
        private static synchronized void show(){ //同步监视器:Window5.class
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + ": 买票,票号为:" + ticket);
                ticket--;
            }
        }
    }
    
    public class WindowTest5 {
        public static void main(String[] args) {
            Window5 t1 = new Window5();
            Window5 t2 = new Window5();
            Window5 t3 = new Window5();
    
            t1.setName("窗口一");
            t2.setName("窗口二");
            t3.setName("窗口三");
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    方式三:Lock锁 (JDK 5.0新增)
    1. 面试题:synchronized 于 Lock锁的异同?
      ① 相同:二者都可以解决线程安全问题
      ② 不同:synchronized机制在执行完全相同的同步代码以后,自动释放同步监视器
      Lock需要手动启动同步(lock()),同时结束也需要手动的实现(unlock())
    package Thread.java;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 解决线程安全问题的方式三:Lock锁 --- JDK5.0新增
     *
     * 1. 面试题:synchronized 于 Lock锁的异同?
     *      ① 相同:二者都可以解决线程安全问题
     *      ② 不同:synchronized机制在执行完全相同的同步代码以后,自动释放同步监视器
     *             Lock需要手动启动同步(lock()),同时结束也需要手动的实现(unlock())
     *
     * @author XiaoQ
     * @create 2022-09-17 22:40
     */
    
    class Window implements Runnable{
        private int ticket = 100;
        private ReentrantLock lock = new ReentrantLock(true); //按每个线程的顺序执行一次
    //    private ReentrantLock lock = new ReentrantLock(true); //随机线程执行
    
        @Override
        public void run() {
            while(true){
                try {
    
                    lock.lock();
    
                    if(ticket > 0){
    //                    try {
    //                        Thread.sleep(100);
    //                    } catch (InterruptedException e) {
    //                        throw new RuntimeException(e);
    //                    }
                        System.out.println(Thread.currentThread().getName() + ": 买票,票号为:" + ticket);
                        ticket--;
                    }else{
                        break;
                    }
                }finally {
                    lock.unlock();
                }
            }
        }
    }
    
    public class LockTest {
        public static void main(String[] args) {
            Window window = new Window();
            Thread t1 = new Thread(window);
            Thread t2 = new Thread(window);
            Thread t3 = new Thread(window);
    
            t1.setName("窗口一");
            t2.setName("窗口二");
            t3.setName("窗口三");
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    3. 线程安全的单例模式(懒汉式)

    package 懒汉式线程安全问题;
    
    /**
     * 使用同步机制将单例模式中的懒汉式改写为线程安全的
     *
     * @author XiaoQ
     * @create 2022-09-17 16:37
     */
    public class BankTest {
    
    }
    
    class Bank{
        private Bank(){}
    
        private static Bank instance = null;
        public static Bank getInstance(){
            // 方式一:效率稍差
    //        synchronized (Bank.class){
    //            if(instance == null){
    //                instance = new Bank();
    //            }
    //            return instance;
    //        }
    
            //方式二:效率更高
            if(instance == null){
                synchronized (Bank.class){
                    if(instance == null){
                        instance = new Bank();
                    }
                }
            }
            return instance;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    4. 死锁问题

    1. 死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己不需要的同步资源么久形成了线程的死锁
    2. 说明:
      ① 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
      ② 我们使用同步时,要避免出现死锁
    package Thread.java;
    
    /**
     * 演示线程的死锁
     * 1. 死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己不需要的同步资源么久形成了线程的死锁
     *
     * 2.说明:
     *  ① 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
     *  ② 我们使用同步时,要避免出现死锁
     *
     *
     * @author XiaoQ
     * @create 2022-09-17 16:59
     */
    public class DeadLock {
        public static void main(String[] args) {
            StringBuffer s1 = new StringBuffer();
            StringBuffer s2 = new StringBuffer();
    
            new Thread(){
                @Override
                public void run() {
                    synchronized(s1){
                        s1.append("a");
                        s2.append(1);
    
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
    
                        synchronized (s2){
                            s1.append("b");
                            s2.append(2);
    
                            System.out.println(s1);
                            System.out.println(s2);
                        }
                    }
                }
            }.start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized(s2){
                        s1.append("c");
                        s2.append(3);
    
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
    
                        synchronized (s1){
                            s1.append("d");
                            s2.append(4);
    
                            System.out.println(s1);
                            System.out.println(s2);
                        }
                    }
                }
            }).start();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    七、线程通信

    涉及到的三个方法:

    1. wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
    2. notify():一旦执行此方法,就会唤醒wait的一个线程;如果有多个线程被wait,就唤醒优先级高的
    3. notifyAll():一旦被执行此方法,就会唤醒所有被wait的线程

    说明:

    1. wait()、notify()、notifyAll()三个方法必须使用在同步代码块或同步方法中
    2. wait()、notify()、notifyAll()三个方法的调用者必须时同步代码块或同步方法中的同步监视器,否则会出现IllegalMonitorStateException异常
    3. wait()、notify()、notifyAll()三个方法时宏定义在Java.lang.Object类中的

    面试题: sleep()和wait()方法的异同?

    1. 相同点:一旦执行,都可以使得当前线程进入阻塞状态
    2. 不同点:
      ① 两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
      ② 调用的要求不同,sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块或同步方法中
      ③ 关于是否释放同步监视器:如果两个都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁
    package Communication;
    
    /**
     * 线程通信的例子:使用2个线程打印1 - 100;线程1和线程2交替打印
     * 涉及到的三个方法:
     * wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
     * notify():一旦执行此方法,就会唤醒wait的一个线程;如果有多个线程被wait,就唤醒优先级高的
     * notifyAll():一旦被执行此方法,就会唤醒所有被wait的线程
     *
     * 说明:
     * 1. wait()、notify()、notifyAll()三个方法必须使用在同步代码块或同步方法中
     * 2. wait()、notify()、notifyAll()三个方法的调用者必须时同步代码块或同步方法中的同步监视器,
     *    否则会出现IllegalMonitorStateException异常
     * 3. wait()、notify()、notifyAll()三个方法时宏定义在Java.lang.Object类中的
     *
     * 面试题:sleep()和wait()方法的异同?
     * 1. 相同点:一旦执行,都可以使得当前线程进入阻塞状态
     * 2. 不同点:① 两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
     *           ② 调用的要求不同,sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块或同步方法中
     *           ③ 关于是否释放同步监视器:如果两个都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁
     *
     * @author XiaoQ
     * @create 2022-09-18 8:56
     */
    
    class Number implements Runnable{
    
        private int num = 1;
        private Object obj = new Object();
        @Override
        public void run() {
            while (true) {
                synchronized (obj) {
    
                    obj.notify(); //a
    
                    if (num <= 100) {
                        System.out.println(Thread.currentThread().getName() + ": " + num);
                        num++;
    
                        try {
                           obj.wait(); //调用如下wait()方法的线程进入阻塞状态
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
    
                    } else {
                        break;
                    }
                }
            }
        }
    }
    
    public class CommunicationTest {
        public static void main(String[] args) {
            Number number = new Number();
            Thread t1 = new Thread(number);
            Thread t2 = new Thread(number);
    
            t1.setName("线程1");
            t2.setName("线程2");
    
            t1.start();
            t2.start();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    八、JDK 5.0新增的两种线程的创建方式

    1. 创建线程方式的三:实现Callable接口(JDK 5.0新增)

    1. 创建一个实现Callable的实现类
    2. 实现call()方法,将此线程需要执行的操作声明在call()中
    3. 创建Callable接口实现类的对象
    4. 将此Callable接口的实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
    5. 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
    6. 获取Callable中call()的返回值(可以获得返回值,非必需)

    如何理解实现Callable接口的方式创建多线程币实现Runnable接口创建多线程方式强大?

    1. call()可以有返回值
    2. call()可以抛出异常,被外面操作捕获,获取异常信息
    3. Callable时支持泛型的
    package 创建线程的方式三和四;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    /**
     * 创建线程方式的三:实现Callable接口(JDK 5.0新增)
     *
     * 如何理解实现Callable接口的方式创建多线程币实现Runnable接口创建多线程方式强大?
     * 1. call()可以有返回值
     * 2. call()可以抛出异常,被外面操作捕获,获取异常信息
     * 3. Callable时支持泛型的
     *
     * @author XiaoQ
     * @create 2022-09-18 10:57
     */
    
    //1. 创建一个实现Callable的实现类
    class NumThread implements Callable{
        //2. 实现call()方法,将此线程需要执行的操作声明在call()中
        @Override
        public Object call() throws Exception {
            int sum = 0;
            for (int i = 1; i <= 100 ; i++) {
                if(i % 2 == 0){
                    sum += i;
                    System.out.println(i);
                }
            }
            return sum;
        }
    }
    
    public class ThreadNew {
        public static void main(String[] args) {
            //3. 创建Callable接口实现类的对象
            NumThread numThread = new NumThread();
            //4. 将此Callable接口的实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
            FutureTask futureTask = new FutureTask(numThread);
            //5. 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
            new Thread(futureTask).start();
    
            try {
                // 6. 获取Callable中call()的返回值
                Object sum = futureTask.get(); //get()返回值即为FutureTask构造器参数Callable实现类重写的call()返回值
                System.out.println("总和为:" + sum);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } catch (ExecutionException e) {
                throw new RuntimeException(e);
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    2. 创建线程的方式四:使用线程池

    1. 提供指定线程数量的线程池
    2. 执行指定的线程操作,需要提供实现Runnable接口或Callable接口实现类的对象
    3. 关闭线程池

    好处:

    1. 提高响应速度(减少了创建新线程的时间)
    2. 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
    3. 便于线程管理
      ① corePoolSize:核心池的大小
      ② maximumPoolSize:最大线程数
      ③ keepAliveTime:线程没有任务时最多保持多长时间后会终止
    package 创建线程的方式三和四;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ThreadPoolExecutor;
    
    /**
     * 创建线程的方式四:使用线程池
     *
     * 好处:
     * 1. 提高响应速度(减少了创建新线程的时间)
     * 2. 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
     * 3. 便于线程管理
     *      corePoolSize:核心池的大小
     *      maximumPoolSize:最大线程数
     *      keepAliveTime:线程没有任务时最多保持多长时间后会终止
     *
     * @author XiaoQ
     * @create 2022-09-18 13:24
     */
    class NumberThread implements Runnable{
    
        @Override
        public void run() {
            for(int i = 0; i <= 100; i++){
                if(i % 2 == 0){
                    System.out.println(Thread.currentThread().getName() + ": " + i);
                }
            }
        }
    }
    
    class NumberThread1 implements Runnable{
    
        @Override
        public void run() {
            for(int i = 0; i <= 100; i++){
                if(i % 2 != 0){
                    System.out.println(Thread.currentThread().getName() + ": " + i);
                }
            }
        }
    }
    
    public class ThreadPool {
        public static void main(String[] args) {
            //1. 提供指定线程数量的线程池
            ExecutorService service = Executors.newFixedThreadPool(10); //提供10线程
    
            //设置线程池的属性
            ThreadPoolExecutor service1 = (ThreadPoolExecutor)service;
            service1.setCorePoolSize(15);
    
            //2. 执行指定的线程操作,需要提供实现Runnable接口或Callable接口实现类的对象
            service.execute(new NumberThread());
            service.execute(new NumberThread1());
            //3. 关闭线程池
            service.shutdown();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    九、小结(释放锁和不会释放锁的操作)

    1. 释放锁的操

    1. 当前线程的同步方法、同步代码块执行结束。
    2. 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。
    3. 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。
    4. 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

    2. 不会释放锁的操作

    1. 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
    2. 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。
  • 相关阅读:
    产品的帮助中心怎么建设?关于编辑帮助文档的几个小技巧~
    使用jdk自带的VisualVM分析hprof堆转储文件
    PyCharm安装在C盘,转移到D盘
    构建系列之前端脚手架vite
    SpringBoot入门
    ajax与python flask之间的表单数据传输
    生活中的破窗效应
    MySQL基础终端命令与Python简单操作MySQL
    TiDB Data Migration 产品简介
    06 逻辑回归
  • 原文地址:https://blog.csdn.net/weixin_52068218/article/details/126899536