美文网首页
Java 基础-线程

Java 基础-线程

作者: JackSpeed | 来源:发表于2021-10-28 22:25 被阅读0次

线程的生命周期

  1. 新建(创建线程对象)
  2. 就绪(线程创建之后,其他线程调用此线程对象的start方法,线程位于可运行线程池中等待获取CPU使用权)
  3. 运行 (获得CPU使用权后执行程序代码)
  4. 阻塞(由于某种原因放弃CPU使用权暂停运行,从此状态可进入就绪状态,比如sleep、wait之后。sleep会自动进入就绪状态,wait需要外界调用notify或者notifyAll才行进入就绪状态)
  5. 死亡(run方法执行结束或者run方法执行异常)

sleep、wait、join、yield方法的区别

  1. sleep是Thread类的静态本地方法,wait是object的静态本地方法
  2. sleep执行之后线程进入阻塞状态,它不会释放锁且不需要主动唤醒,sleep结束之后自动进入就绪状态,sleep期间会让出CPU执行时间
  3. wait执行之后线程也会进入阻塞状态,它会释放锁加入线程等待队列中,等待被notify,被唤醒之后才能重新进入就绪状态等到获得CPU执行权;sleep一般用于当前线程休眠或轮询暂停,wait多用于多个线程质检的通信
  4. yield方法执行之后线程会进入就绪状态,马上释放CPU执行权,但是依然保留CPU执行资格,所以下次线程调度可能还会让这个线程得到执行权继续执行
  5. join执行之后线程进入阻塞状态,例如在线程B中调用A线程的join方法,那么线程B会进入阻塞状态直到线程A执行结束或者中断退出
public static void main(String[] args){
  Thread ta=new Thread(new Runnable(){
        public void run(){
              try{
                 Thread.sleep(10000);
              }catch(Exception e){
                e.print;
              }
            sout("线程A执行结束");
          }
    });
 ta.start();
ta.join():
sout("主线程执行结束");
}
//输出结果为  
//线程A执行结束
//线程B执行结束

谈谈你对线程安全的理解

线程安全本质上指的应该是内存安全,也可以说是线程访问内存对象安全。(定义:当多个线程访问堆里面的同一个对象时,如果不进行额外的同步控制或者其他协调操作,调用这对象的行为都可以获得正确的结果,那么久可以说这个对象是线程安全的

堆:堆是进程和线程共享的内存区域,有全局堆和局部堆之分,全局堆就是所有没分配的内存空间,局部堆是给用户分配的空间。对在操作系统对进程初始化是分配,运行过程中也可以向操作系统额外索要堆但是用完额外堆之后要还给操作系统,不然会造成内存泄漏。

栈:栈是每个线程独有的,它保存了当前线程的运行状态和局部变量;栈在线程开始的时候初始化,每个线程的栈是互相独立的,所以线程栈内部的对象是线程安全的,栈也是线程安全的。操作系统在切换线程是会自动切换栈。栈空间不需要显式分配和释放

Thread和runnable的区别

Thread是表示线程,是类;runnable是接口,接口可以多实现,类只能单继承,这两个类都可以实现线程的操作,只不过Thread提供的操作丰富一些而已

说说你对守护线程的理解

守护线程可以说是后台线程,它为所有非守护线程提供服务,它的死亡无关重要,它的终止是自身无法控制的。
例子:GC垃圾回收线程就是一个典型的守护线程
应用场景:需要支持其他线程功能时或者程序结束时这个线程必须正常且立刻关闭,这两种场景可用守护线程
设置操作:thread.setDaemon(true),必须在thrread.start()之前设置,在守护线程中创建的线程也是守护线程;ExecuteService会将守护线程转换成为守护线程。
由于自身的不稳定性,所以磁盘IO、数据库读写、计算逻辑等操作禁止使用守护线程

ThreadLocal的原理和使用场景

每个Thread对象都有一个ThreadLocalMap类型的变量threadlocals,它存储本线程中所有的ThreeadLocal对象和ThreadLocal对应的值,ThreadLocalMap是由多个Entry对象构成,Entry继承WeakReference<ThreadLocal<T>>,一个Entry对象由ThreadLocal对象和Object组成,ThreadLocal作为KEY,并且是一个弱引用(当没有指向key的强引用之后,该key就会被垃圾回收器回收)
当执行ThreadLocal的set方法时,ThreadLocal会先获取当前线程对象,然后再获取线程对象的ThreadLocalMap,然后再以当前ThreadLocal为key将值存储在当前线程的ThreadLocalMap中,get方法也是类似的先获取ThreadLocalMap,在根据key获取value。
由于每个线程独有一个ThreadLocalMap,多个线程间互不影响,所以不存在线程安全问题。
使用场景:

  1. 在进行对象跨层级传递、跨层级传参是,使用ThreadLocal可避免多次传递带来的麻烦,大伯层级约束
  2. 线程间数据隔离
  3. 进行事务操作,存储线程的事务信息
  4. 数据库链接、session回话管理

ThreadLcoal内存泄漏的原因是什么?如何避免?

原因:由于ThreadLocalMap的生命周期和线程的保持一致,当弱引用的ThreadLocal作为key时,如果没有手动删除调ThreadLocalMap里的值,作为弱引用的ThreadLocal很容易被GC回收掉,从而导致ThreadLocalMap的可以为null,value还一致存在,发生内存泄漏。
避免:

  1. 每次使用完ThreadLocal都主动调用它的remove()方法
  2. 想ThreadLocal定义成private static,这样就之一存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而删除到value。

并发的三大特性

例子:int i=0,i执行i++的工作流程
变量赋值步骤
a. 将i变量从内存中读取到寄存器
b. 运算i++
c. 将结果写入寄存器
d. 将寄存器的值更新到内存中
如果a、b、c、d是在一个CPU时间片内执行,那么就能满足线程安全的条件

  1. 原子性:java的原子性跟数据库事务的原子性是一样的,要么都执行,要么都不执行
  2. 可见性:线程对共享变量的修改,其他线程是立即可见的:也就是说A线程修改了i变量,其他线程立即能读取到最新的i的值
  3. 有序性:有序性是指虚拟机执行代码时,对于改变顺序之后对结果没影响的代码,虚拟机不一定会按照编写顺序来执行。可使用volatile关键字禁止指令中排序保证有序性
    只有同时满足了这三个特性,并发处理才能线程安全

synchronize可以保证原子性、可见性、有序性
volatile可以保证可见性、有序性
final可以保证可见性

为什么使用线程池?线程池有哪些常用参数?

  1. 使用线程池可以提高线程使用率,降低创建和销毁线程的开销
  2. 使用线程池可以提高响应速度,因为当任务到达时可以立即执行免去了创建线程的步骤
  3. 提高了线程的可管理性
  4. 使用线程池可以统一分配调优监控线程

参数:
corePoolSize:核心线程数,也就是正常情况下工作的线程,这些线程创建后不会被关闭,是常驻线程而非守护线程
maximumPoolSize:代表最大线程数,他与核心线程数相对应,表示最大允许被创建的线程数量,比如当前核心线程用完了,任务继续流入,此时就需要创建新线程处理任务,但是新创建的线程数量和核心线程数的合不会大于最大线程数
keepAliveTime、unit:代表超出核心线程数之外的其他线程的空闲存活时间
workQueue:代表任务队列,用来存放待执行的任务,假设核心线程使用完了,还继续有任务流入,那么新流入的任务会全部放入队列中,直到整个队列被放满且持续有任务流入,此时会创建新的线程处理任务
ThreadFactory:表示线程工厂,用来产生线程任务,可以使用默认的线程工厂也可以自定义线程工厂,一个工厂内创建的线程有相同的优先级,在工厂内可自定义线程分组等属性。
Handler:表示任务拒绝策略,有两种情况,第一种是当调用shutdown等方法主动关闭线程池后,此时线程池内部还有没执行完的任务,但是由于线程池已经关闭,我们想继续提交任务就会遭到拒绝。另外一种是当已有线程数达到最大线程数量时,线程池已经没有能力处理新任务,这时也会遭到拒绝

线程池处理流程

  1. 创建线程池,提交任务
  2. 判断核心线程池是否已满,如果没满,创建和核心线程执行任务;如果已满则将任务放入workQueue;
  3. 在第2步放入任务时判断任务队列是否已满,如果未满则放入队列中等待被核心线程执行,如果已满则判断当前线程数量是达到最大线程数,如果已达到则根据拒绝策略做出处理,如果为达到最大线程数则创建临时线程执行任务

线程池中阻塞队列的作用是什么?为什么是先添加队列而不是先创建最大线程

  1. 一般的队列只能保证作为一个有限长度的缓冲区,仅仅是作为一个容器,如果超过了长度则无法保存当前任务了。阻塞队列通过阻塞可以保存住当前想要继续入队的任务,它可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态释放CPU资源,阻塞队列自带阻塞和唤醒功能,不需要额外的处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活,不至于一直占用CPU资源。
  2. 在创建新线程的时候,是需要获取全局锁的,这个时候就需要阻塞其他线程,影响了整体的效率,而使用阻塞队列可以暂存任务,当前面的线程工作完了可以继续处理当前阻塞的任务;省去了创建和销毁新线程的开销。例如公司是个员工,有十个任务,当第十一个任务进来时,先招人还是新任务适当延期等待前面十个人中有人完成任务呢?

结论
1. 阻塞队列的主要作用是阻塞线程、 唤醒线程、降低CPU资源占用率、保存住当前想要入队的任务
2. 因为创建和销毁线程需要额外的资源开销,使用队列的话可以降低资源消耗

GC如果判断对象可以被回收

可通过以下两种方法判断

  1. 引用计数法(每个对象有一个引用属性,新增引用则计数器+1、引用释放则-1,计数为0时则可以回收
  2. 可达性分析发(从GC roots开始向下搜索,如果一个对象到GC roots没有任何引用链路则证明该对象是不可用的可以被回收;对象的回收不是立即回收,对象有一次自我拯救的机会,具体过程是:GC会判断对象是否重写了finalize方法,如果没重写则直接回收,重写了则看对象是否执行过finalize方法,如果未执行则将其放入F-Queue队列由低优先级线程执行对象的finalize方法,执行finalize方法完毕之后GC会再次判断对象是否可达,如果不可达则回收,如果可达则表示对象存在其他引用,对象被复活——finalize方法只会执行一次
    GC Roots的对象有:
    a. 虚拟机站中引用的对象
    b. 方法去中静态属性引用的对象
    c. 方法区中常量引用的对象
    d. 本地方法栈中JNI引用的对象

相关文章

  • 技术体系

    一,java核心 java基础,jvm,算法,多线程,设计模式 Java基础:java基础相关,全栈java基础 ...

  • Java多线程目录

    Java多线程目录 Java多线程1 线程基础Java多线程2 多个线程之间共享数据Java多线程3 原子性操作类...

  • android 多线程 — 线程的面试题和答案

    这里都是我从各个地方找来的资料,鸣谢: Java多线程干货系列—(一)Java多线程基础 JAVA多线程和并发基础...

  • Android中的多线程

    1. Java多线程基础 Java多线程,线程同步,线程通讯 2. Android常用线程 HandlerThre...

  • Java基础

    Java基础 集合基础 集合框架 多线程基础 多线程框架 反射 代理 集合基础 ArrayList LinkedL...

  • java多线程相关

    (一) 基础篇 01.Java多线程系列--“基础篇”01之 基本概念 02.Java多线程系列--“基础篇”02...

  • java线程入门基础(二)

    java线程入门基础(二) 一、认识Java里的线程 1.1 Java里的程序天生就是多线程的 一个Java程序从...

  • Java多线程高级特性(JDK8)

    [TOC] 一、Java多线程 1.Java多线程基础知识 Java 给多线程编程提供了内置的支持。一条线程指的是...

  • 高并发Java

    高并发Java(1):前言 高并发Java(2):多线程基础 高并发Java(3):Java内存模型和线程安全 高...

  • Java-并发编程知识点总结

    目录: 线程基础 线程池 各种各样的锁 并发容器 原子类 Java 内存模型 线程协作 AQS 框架 一、线程基础...

网友评论

      本文标题:Java 基础-线程

      本文链接:https://www.haomeiwen.com/subject/wntuyltx.html