当前位置:   article > 正文

Java —— 线程

Java —— 线程

Java —— 线程

1.基础概念

  • 程序(program):为完成特定的任务,选用某一种语言编写的一组指令的集合,即指一段静态的代码,静态对象。

  • 进程(process):程序的一次执行过程,或是正在内存中运行的应用程序。程序是静态的、进程是动态的

    • 进程是操作系统调度和分配资源的最小单位。

    • 注意:不同的进程之间是不共享内存的、进程之间的数据交换和通信的成本很高。

  • 线程(thread):进程可以进一步细化为线程,是运行中进程的一条或多条执行路径。

    • 线程作为CPU调度和执行的最小单位。
  • 线程调度策略

    • 分时调度:所有线程轮流使用CPU的使用权,并且平均分配每个线程占用CPU的时间
    • 抢占式调度:让优先级高的线程以较大的概率优先使用CPU。如果线程的优先级相同,那么会随机选择一个(线程随机性)。
    • Java使用的抢占式调度
  • 单核CPU与多核CPU

    • 单核CPU:在一个时间单元内,只能执行一个线程的任务。
    • 多核CPU:多加几个单核CPU,即多核的CPU。
      • 多核的两方面损耗:
        • 一个是多个核心的其他共用资源限制。
        • 另一个是多核CPU之间的协调管理损耗。
  • 并行与并发

    • 并行:指两个或多个事件在同一时刻发生(同时发生)。指在同一时刻,有多条指令多个CPU上同时执行。
      比如:多个人同时做不同的事。
    • 并发:指两个或多个事件在同一个时间段内发生。即在一段时间内,有多条指令单个CPU上快速轮换、交替执行,使得在宏观上具有多个进程同时执行的效果。

2.如何创建多线程(重点理解)

  • 方式1:继承Thread类
  • 方式2:实现Runnable接口
  • 方式3:实现Callable接口(jdk5.0新增)
  • 方式4:使用线程池(jdk5.0新增)

3.Thread类的常用方法、线程的生命周期

  • 线程中的构造器

    • public Thread() :分配一个新的线程对象。
    • public Thread(String name) :分配一个指定名字的新的线程对象。
    • public Thread(Runnable target) :指定创建线程的目标对象,它实现了Runnable接口中的run方法
    • public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。
  • 线程中的常用方法:

    • start():①启动线程、②调用线程的run()
    • run():将线程要执行的操作,声明在run()中
    • currentThread():获取当前执行代码对应的线程
    • getName():获取线程名
    • setName():设置线程名
    • sleep(long millis):静态方法,调用时,可以使得当前线程睡眠指定的毫秒数
    • yield():一旦执行此方法,就释放CPU的执行权
    • join():在线程a中通过线程b调用join(),意味着线程a进入阻塞状态,直到线程b执行结束,线程a才结束阻塞状态,继续执行
    • isAlive():判断当前线程是否存活
    • stop():强制结束一个线程的执行,直接进入死亡状态,导致run()有未执行结束的代码。stop()会导致释放同步监视器,导致线程安全问题。(过时方法:不建议使用)
    • void suspend() / void resume() :可能造成死锁,(过时方法:不建议使用)
  • 线程的优先级:

    • getPriority():获取线程的优先级
    • setPriority():设置线程的优先级
    • Thread类内部声明的三个常量
      • MAX_PRIORITY(10):最高优先级(CPU执行概率较大)
      • MIN_PRIORITY(1):最低优先级
      • NORM_PRIORITY(5):普通优先级,默认情况下main线程具有普通优先级
  • 线程的生命周期

    • New(新建) – Runnable(运行,分为准备+运行) – Terminated(死亡)
    • 运行阶段:Blocked(锁阻塞) + Waiting(无限等待) + Timed_Waiting(计时等待)

4.如何解决线程安全问题(同步机制)

  • 什么是线程安全问题:多个线程操作共享数据,就有可能出现安全问题

  • 解决安全问题的几种方式

    • 同步机制(synchronized):①同步代码块 ②同步方法

      • 在实现Runnable接口的方式中,同步监视器可以考虑使用this。

      • 在继承Thread类的方式中,同步监视器要慎用this(可能存在多个对象),建议使用:当前类.class。

      • 非静态的同步方法,默认同步监视器是this

      • 静态的同步方法,默认同步监视器是当前类

    • jdk5.0新增:Lock接口及其实现类

5.同步机制相关的问题

  • 懒汉式的线程安全的写法

    public class BankTest {
    
        static Bank b1 = null;
        static Bank b2 = null;
    
        public static void main(String[] args) {
            Thread t1 = new Thread(){
                @Override
                public void run() {
                    b1 = Bank.getInstance();
                }
            };
    
            Thread t2 = new Thread(){
                @Override
                public void run() {
                    b2 = Bank.getInstance();
                }
            };
    
            t1.start();
            t2.start();
    
            try {
                t1.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
    
            try {
                t2.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
    
            System.out.println(b1);
            System.out.println(b2);
            System.out.println(b1 == b2);
        }
    }
    
    class Bank{
        private Bank(){}
    
        private static volatile Bank instance = null;  // volatile避免指令重排
    
        //实现线程安全的方式1
    //    public static synchronized Bank getInstance() {  //同步监视器默认为Bank.class
    //        if (instance == null){
    //
    //            try {
    //                Thread.sleep(1000);
    //            } catch (InterruptedException e) {
    //                throw new RuntimeException(e);
    //            }
    //
    //            instance = new Bank();
    //        }
    //        return instance;
    //    }
    
    //    //实现线程安全的方式2
    //    public static Bank getInstance() {  //同步监视器默认为Bank.class
    //        synchronized (Bank.class) {
    //            if (instance == null){
    //
    //                try {
    //                    Thread.sleep(1000);
    //                } catch (InterruptedException e) {
    //                    throw new RuntimeException(e);
    //                }
    //
    //                instance = new Bank();
    //            }
    //        }
    //        return instance;
    //    }
    
    
        //实现线程安全的方式3:相较于1和2,优化,效率更高。为了避免出现指令重排,需要将instance声明为volatile
        public static Bank getInstance() {  //同步监视器默认为Bank.class
            if (instance == null) {  //instance为null,直接return,效率更高
                synchronized (Bank.class) {
                    if (instance == null) {
    
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
    
                        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
    • 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
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
  • 同步机制会带来的问题:死锁

    • 如何看待死锁?

      不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了现成的死锁。(双方需要的资源都被占用,也不打算释放,就形成了死锁),编写程序时,要避免死锁。

    • 诱发死锁的原因 及 如何避免死锁?

      • 互斥条件 :互斥条件基本上无法被破坏。因为线程需要通过互斥解决线程安全问题。
      • 占用且等待 :可以考虑一次性申请所有的资源,这样就不存在等待的问题。
      • 不可抢夺(或不可抢占):占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放已经占用的资源
      • 循环等待 :可以将资源改为线性顺序。申请资源时,先申请序号较小的,这样避免循环等待问题。

6.线程间的通信

  • 线程间通信的理解

    • 当需要多个线程来共同完成一件任务时,我们希望其有规律的执行,那么多谢线程之间需要一些同行机制,可以协调它们的工作,以此实现多线程共同操作同一份数据
  • 涉及到三个方法的使用:

    • wait():线程一旦执行此方法,就进入等待状态。同时,会释放对同步监视器的调用
    • notify():一旦执行此方法,就会唤醒被wait()的线程中优先级最高的一个线程(若多个线程的优先级相同,则随机唤醒一个)。
      被唤醒的线程从当初被wait()的位置继续执行。
    • notifyAll():一旦执行此方法,就会唤醒所有被wait()的线程。
  • 注意点:

    • 上述三个方法必须在(synchronized)同步代码块或同步方法中使用。
      (超纲:Lock需要配合Condition实现线程间的通信)
    • 此三个方法的调用者,必须是同步监视器。否则会报IllegalMonitorStateException异常
    • 此三个方法声明在Object类中。
  • wait() 和 sleep()的区别?(高频面试题)

    • 相同点:一旦执行,当前线程都会进入阻塞状态
    • 不同点:
      • 声明的位置:
        wait():声明在Object中
        sleep():声明在Thread类中,是静态的
      • 使用的场景不同:
        wait():只能使用在同步代码块中或同步方法中
        sleep():可以在任何需要使用的场景
      • 使用在同步代码块或同步方法中:
        wait():一旦执行,会释放同步监视器
        sleep():一旦执行,不会释放同步监视器
      • 结束阻塞的方式:
        wait():到达指定时间自动结束阻塞 或 通过被notify唤醒,结束阻塞
        sleep():到达指定时间自动结束阻塞
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/在线问答5/article/detail/924450
推荐阅读
相关标签
  

闽ICP备14008679号