Java基础知识点

创建线程有哪几种方式?

创建线程有三种方式,分别是继承Thread类实现Runnable接口、实现Callable接口。

通过继承Thread类来创建并启动线程的步骤如下:

  1. 定义Thread类的子类,并重写该类的run()方法,该run()方法将作为线程执行体。
  2. 创建Thread子类的实例,即创建了线程对象。
  3. 调用线程对象的start()方法来启动该线程。

通过实现Runnable接口来创建并启动线程的步骤如下:

  1. 定义Runnable接口的实现类,并实现该接口的run()方法,该run()方法将作为线程执行体。
  2. 创建Runnable实现类的实例,并将其**作为Thread的target来创建Thread对象,**Thread对象为线程对象。
  3. 调用线程对象的start()方法来启动该线程。

通过实现Callable接口来创建并启动线程的步骤如下:

  1. 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值。然后再创建Callable实现类的实例。
  2. 使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
  3. 使用FutureTask对象作为Thread对象的target创建并启动新线程。
  4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

通过继承Thread类、实现Runnable接口、实现Callable接口都可以实现多线程,不过实现Runnable接口与实现Callable接口的方式基本相同,只是**Callable接口里定义的方法有返回值,可以声明抛出异常而已。**因此可以将实现Runnable接口和实现Callable接口归为一种方式。

线程是否可以重复启动,会有什么后果?

只能对处于新建状态的线程调用start()方法,否则将引发IllegalThreadStateException异常。

当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时它和其他的Java对象一样,仅仅由Java虚拟机为其分配内存,并初始化其成员变量的值。此时的线程对象没有表现出任何线程的动态特征,程序也不会执行线程的线程执行体。

当线程对象调用了start()方法之后,该线程处于就绪状态,Java虚拟机会为其创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。至于该线程何时开始运行,取决于JVM里线程调度器的调度。

介绍一下线程的生命周期

Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态:

  • NEW: 初始状态,线程被创建出来但没有被调用 start()
  • RUNNABLE: 运行状态,线程被调用了 start()等待运行的状态。
  • BLOCKED :阻塞状态,需要等待锁释放。
  • WAITING:等待状态,表示该线程需要等待其他线程做出一些特定动作(通知或中断)。
  • TIME_WAITING:超时等待状态,可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。
  • TERMINATED:终止状态,表示该线程已经运行完毕。

如何实现线程同步?

  1. 同步方法

    即有synchronized关键字修饰的方法,由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。需要注意, synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。

  2. 同步代码块

    即有synchronized关键字修饰的语句块,被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。需值得注意的是,同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

  3. ReentrantLock

    Java 5新增了一个java.util.concurrent包来支持同步,其中ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。需要注意的是,ReentrantLock还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,因此不推荐使用。

  4. volatile

    volatile关键字为域变量的访问提供了一种免锁机制,使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,因此每次使用该域就要重新计算,而不是使用寄存器中的值。需要注意的是,volatile不会提供任何原子操作,它也不能用来修饰final类型的变量。

  5. 原子变量

    在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,使用该类可以简化线程同步。例如AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),但不能用于替换Integer。可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。

说一说Java多线程之间的通信方式

  • wait()、notify()、notifyAll()
  • await()、signal()、signalAll()
  • BlockingQueue

说一说sleep()和wait()的区别

  1. sleep()是Thread类中的静态方法,而wait()是Object类中的成员方法;
  2. sleep()可以在任何地方使用,而wait()只能在同步方法或同步代码块中使用;
  3. sleep()不会释放锁,而wait()会释放锁,并需要通过notify()/notifyAll()重新获取锁。

说一说notify()、notifyAll()的区别

  • notify()

    用于唤醒一个正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行。

  • notifyAll()

    用于唤醒所有正在等待相应对象锁的线程,使它们进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行

日志的等级划分

  • ALL 最低等级的,用于打开所有日志记录。
  • TRACE designates finer-grained informational events than the DEBUG.Since:1.2.12,很低的日志级别,一般不会使用。
  • DEBUG 指出细粒度信息事件对调试应用程序是非常有帮助的,主要用于开发过程中打印一些运行信息。
  • INFO 消息在粗粒度级别上突出强调应用程序的运行过程。打印一些你感兴趣的或者重要的信息,这个可以用于生产环境中输出程序运行的一些重要信息,但是不能滥用,避免打印过多的日志。
  • WARN 表明会出现潜在错误的情形,有些信息不是错误信息,但是也要给程序员的一些提示。
  • ERROR 指出虽然发生错误事件,但仍然不影响系统的继续运行。打印错误和异常信息,如果不想输出太多的日志,可以使用这个级别。
  • FATAL 指出每个严重的错误事件将会导致应用程序的退出。这个级别比较高了。重大错误,这种级别你可以直接停止程序了。
  • OFF 最高等级的,用于关闭所有日志记录。

异常

NullPointerException空指针操作一个 null 对象的方法或属性时会抛出这个异常
ArrayIndexOutOfBoundsException数组越界
IllegalArgumentException参数非法
BufferOverflowException缓存溢出
ClassNotFoundException在编译时无法找到指定的类
ClassCastException类型强转java类型转换错误异常
ExceptionInInitializerError静态初始值或静态变量初始值期间发生异常
UnsatisfiedLinkErrorJNI加载dll或者so文件时未找到
NoClassDefFoundError在编译时能找到合适的类而在运行时不能找到合适的类
OutofOutofMemoryError内存出现异常的一种异常这不是程序能控制的是指要分配的对象的内存超出了当前最大的堆内存需要调整堆内存大小-Xmx以及优化程序

Java内存模型

Java内存模型结构分为

  • 线程共享区:堆、方法区
  • 线程私有区:虚拟机栈、本地方法栈、程序计数器

堆用于存放对象实例和数组,由于堆是用来存放对象实例,因此堆也是垃圾收集器管理的主要区域,故也称为 GC堆。由于现在的垃圾收集器基本都采用分代收集算法,所以堆的内部结构只包含新生代和老年代

方法区

方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

  • 方法区通常和永久区(Perm)关联在一起,但永久代与方法区不是一个概念,只是有的虚拟机用永久代来实现方法区,这样就可以用永久代GC来管理方法区,省去专门内存管理的工作
  • 根据Java虚拟机规范的规定,当方法区无法满足内存分配的需求时,将抛出 OutOfMemoryError 异常

堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (几乎所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

  • 虚拟机栈: 每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
  • 本地方法栈: 和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。

所以,为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的。


标题:Java基础知识点
作者:joker
地址:https://upcjoker.top/articles/2022/09/20/1663659675169.html

    评论
    0 评论
avatar

取消