Java线程基础

创建线程的三种方式:

  • ​ 继承Thread

  • ​ 实现Runnable接口,然后交给Thread执行

  • ​ 实现Callable接口,通过FutureTask,可以拿到回调值

    严格来说只有前两种(如Thread源码中注释所说)第三种其实是被包装成FutureTask交给Thread执行,而FutureTask实现RunnableFuture,RunnableFuture继承自Runnable)

线程相关的一些方法

  • sleep期间不会释放锁,结束后会进入就绪状态,调用sleep时如果interrupted为true,则会抛出InterruptedException异常,并清除interrupt状态为false,想要彻底打断,需要在捕获异常时再调用interrupt()

  • wait会释放锁,需要等待别人唤醒才能进入就绪状态

  • interrupt和谐的结束线程,设置标识

  • join可以实现顺序执行线程,直接获取当前线程的执行权执行自己,自己执行完后再执行当前线程

  • setDaemon(),为当前线程设置守护线程,当前线程结束,守护线程也会结束

线程的一些状态

image.png

注:**在显式锁调用lock()获取不到锁时会调用LockSupport.park()进入等待或者等待超时状态,不会进入阻塞态,只有使用synchronized关键字获取不到锁时才会进入阻塞态,等待状态是自己调用方法主动进入,而阻塞状态是被动进入

ThreadLocal理解

  • 每个Thread中都持有一个ThreadLocalMap对象,这个对象存储了一组以ThreadLocal对象为键,以本地线程变量为value的键值对,叫做Entry的数组。可以在不同的线程中通过同一个ThreadLocal的set方法设置不同的线程局部变量,键都是同一个ThreadLocal对象,但是值保存在不同的Thread的ThreadLocalMap里,这样再通过同一个ThreadLocal的get方法取的时候,同一个key,在不同的线程却从不同的ThreadLocalMap取,值也是各自线程之前保存的局部变量值,所以ThreadLocal能够实现线程间的“数据隔离”,获取当前线程的局部变量值,不受其他线程影响~
  • 每个Thread维护着一个ThreadLocalMap的引用
  • ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储
  • 调用ThreadLocal的set()方法时,实际上就是往自己线程Thread对象的ThreadLocalMap设置值,key是ThreadLocal对象,值是传递进来的对象
  • 调用ThreadLocal的get()方法时,实际上就是从各自的Thread对象的ThreadLocalMap获取值,key是ThreadLocal对象
  • ThreadLocal本身并不存储值,它只是作为一个key来让线程Thread对象从各自的ThreadLocalMap获取value。

线程池

  • 线程池的优势:

    • 降低资源消耗,通过重复利用自己创建的线程降低线程的创建和销毁造成的消耗
    • 提高响应速度,当任务到达时,任务可以不需要等到线程创建就可以立即执行
    • 提高线程的可管理性,线程是稀缺资源,无限的创建,不仅消耗资源,还会降低系统稳定性,线程池可以统一分配,调优和监控
  • 线程池参数

    • 核心线程数

    • 最大线程数

    • 线程空闲时存活

    • 存活时间单位

    • 阻塞队列

    • 创建线程工厂

    • 4种拒绝策略:1.默认策略直接抛出异常。2.抛弃这个任务。3.抛弃阻塞队列最靠前的。4.用调用者所在线程执行任务

  • 线程工作机制:

    • 如果当前运行的线程少于核心线程数,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。
    • 如果运行的线程等于或多于核心线程数,则将任务加入阻塞队列BlockingQueue。
    • 如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务。
    • 如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。
  • 如何配置线程池

    • CPU密集型:CPU在不停的计算,从内存中取值纯计算。

      尽量使用较小的线程池,最大线程数一般为CPU核心数+1(由于内存比较紧张,会把一部分磁盘划分为虚拟内存,线程处理的数据同时存在虚拟内存,磁盘中时,操作系统需要把磁盘中的数据调度到虚拟内存中,磁盘中读取速度比虚拟内存中慢,此时就会把线程标记为页缺失状态,会等数据调度过来后再唤醒线程,这样会有空闲CPU),CPU核心数获取:Runtime.getRuntime().availableProcessors(), 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。

    • IO密集型:网络通讯、读写磁盘

      可以使用稍大的线程池,一般为2*CPU核心数。 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。

    • 混合型:以上两种都有,这种情况下,可以将任务分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理。 如果拆分之后两个任务执行时间有数据级的差距,那么拆分没有意义。
      因为先执行完的任务就要等后执行完的任务,最终的时间仍然取决于后执行完的任务,而且还要加上任务拆分与合并的开销,如果拆分之后两个任务执行时间差不多,那拆分效率更高

你可能感兴趣的:(Java线程基础)