JavaSE学习笔记----多线程

本文详细介绍了多线程的定义、并发执行原理、线程生命周期的五个阶段,包括新建、就绪、运行、阻塞和死亡。还探讨了线程优先级、创建方式、线程池及其应用,以及如何处理并发安全问题,如synchronized关键字的使用。
摘要由CSDN通过智能技术生成

多线程


定义

多个单一顺序执行的流程并发运行,会造成“感官上的同时运行”的效果。


并发

多个线程实际运行是走走停停的。线程调度程序会将CPU运行时间划分为若干个时间片段并尽可能均匀的分配给每个线程,拿到时间片的线程被CPU执行这段时间。当超时后线程调度程序会再次分配一个时间片段给一个线程使得CPU执行它。如此反复。由于CPU执行时间在纳秒级别,我们感觉不到切换线程运行的过程。所以微观上走走停停,宏观上感觉一起运行的现象成为并发运行!


用途

  • 当出现多个代码片段执行顺序有冲突时,希望它们各干各的时就应当放在不同线程上"同时"运行。
  • 一个线程可以运行,但是多个线程可以更快时,可以使用多线程运行

线程的生命周期

线程的5种状态

线程生命周期图
在这里插入图片描述

1 new新建状态

使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

注意:启动该线程要调用该线程的start方法,而不是run方法!!!线程调用完start方法后会纳入到系统的线程调度器程序中被统一管理。线程调度器会分配时间片段给线程,使得CPU执行该线程这段时间,用完后线程调度器会再分配一个时间片段给一个线程,如此反复,使得多个线程都有机会执行一会,做到走走停停,并发运行。线程第一次被分配到时间后会执行它的run方法开始工作。
每个线程启动后,虚拟机就会为其分配一块栈内存。每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存。每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。

2 Runnable就绪状态

当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

3 Runnung运行状态

如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

4 Block阻塞状态

如果一个线程执行了sleep(睡眠时间:ms),失去所占用资源之后,该线程就从运行状态进入阻塞状态。
sleep存在异常:InterruptException;
sleep时间到了之后线程进入就绪状态;
sleep可以模拟网络延时、倒计时等;
每一个对象都有一个无形的锁,sleep不会释放锁。wait会释放锁,但调用它的前提是当前线程占有锁(即代码要在synchronized中)。
sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能调用。它们都可以被interrupted方法中断。

5 Dead死亡状态

一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。sleep(时间)指定当前线程阻塞的毫秒数:


线程的优先级

线程start后会纳入到线程调度器中统一管理,线程只能被动的被分配时间片并发运行,而无法主动索取时间片.线程调度器尽可能均匀的将时间片分配给每个线程。
线程有10个优先级,使用整数1-10表示;
1为最小优先级,10为最高优先级,5为默认值;
调整线程的优先级可以最大程度的干涉获取时间片的几率。

线程的创建

创建的两种方式

方式一:继承Thread并重写run方法

优点:结构简单,利于匿名内部类形式创建。
缺点:由于java是单继承的,这会导致继承了Thread就无法再继承其他类去复用方法;定义线程的同时重写了run方法,这等于将线程的任务定义在了这个线程中导致线程只能干这件事。重用性很低。

方式二: 实现Runnable接口单独定义线程任务

主线程

java中所有的代码都是靠线程执行的,main方法也不例外。JVM启动后会创建一条线程来执行main方法,该线程的名字叫做"main",所以通常称它为"主线程"。 我们自己定义的线程在不指定名字的情况下系统会分配一个名字,格式为"thread-x"(x是一个数)。

注意:主线程结束时,并不表示进程结束 。只有当一个进程中所有普通线程结束时,进程才结束。


线程常用API

方法名static功能
start()启动一个新线程,在新的线程运行 run 方法中的代码
run()新线程启动后会 调用的方法
getId()获取线程的 id(唯一)
setPriority(int priority)设置线程的优先级
isInterrupted()判断线程是否被中断
interrupt()中断线程(如果被打断线程正在 sleep,wait,join 会导致被打断的线程抛出 InterruptedException,并清除打断标记;如果打断正常运行的线程, 不会清空打断状态)
currentThread()获取当前正在执行的线程
sleep(long n)让当前执行的线程休眠n毫秒, 休眠时让出 cpu 的时间片给其它线程
yield()提示线程调度器 让出当前线程对CPU的使用
setDaemon(boolean on)将普通线程设置为守护线程(当一个java进程中的所有普通线程都结束时,该进程就会结束,此时会强制杀死所有正在运行的守护线程。)

多线程并发安全问题

当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致操作临界资源的顺序出现混乱严重时可能导致系统瘫痪。
临界资源:操作该资源的全过程同时只能被单个线程完成。

如何解决?

将并发操作同一临界资源的过程改为同步执行就可以有效的解决并发安全问题。

synchronized关键字

synchronized有两种使用方式
  • 当一个方法使用synchronized修饰后,这个方法称为"同步方法"。多个线程不能同时在方法内部执行.只能有先后顺序的一个一个进行。
  • 同步块,可以更准确的锁定需要排队的代码片段。有效的缩小同步范围可以在保证并发安全的前提下尽可能的提高并发效率.同步块可以更准确的控制需要多个线程排队执行的代码片段
在静态方法上使用synchronized

当在静态方法上使用synchronized后,该方法是一个同步方法.由于静态方法所属类,所以一定具有同步效果。
静态方法使用的同步监视器对象为当前类的类对象(Class的实例)。

互斥锁

当多个线程执行不同的代码片段,但是这些代码片段之间不能同时运行时就要设置为互斥的。
使用synchronized锁定多个代码片段,并且指定的同步监视器是同一个时,这些代码片段之间就是互斥的。

死锁

死锁的产生:两个线程各自持有一个锁对象的同时等待对方先释放锁对象。这时就会产生死锁现象。

如何解决死锁
  • 方式一:尽量避免在持有一个锁的同时去等待持有另一个锁(避免synchronized嵌套)
  • 方式二:当无法避免synchronized嵌套时,就必须保证多个线程锁对象的持有顺序必须一致。即:A线程在持有锁1的过程中去持有锁2时,B线程也要以这样的持有顺序进行。

线程池

简介

什么是线程池

线程池(ThreadPool)是一种基于池化思想管理和使用线程的机制。它是将多个线程预先存储在一个“池子”内,当有任务出现时可以避免重新创建和销毁线程所带来性能开销,只需要从“池子”内取出相应的线程执行对应的任务即可。常见的运用池化思想的有:内存池、数据库连接池。使用线程池的优点如下:

  • 提高线程的利用率
  • 提高程序的相应速度
  • 便于统一管理线程对象

执行流程

corePoolSize 对应线程池中的核心线程数,maximumPoolSize线程池中的最大线程数,workQueue应线程池中的阻塞(等待)队列。线程池关键节点的执行流程如下:

当线程数小于核心线程数时,创建线程。
当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
当线程数大于等于核心线程数,且任务队列已满:若线程数小于最大线程数,创建线程;若线程数大于等于最大线程数,抛出异常,拒绝任务。
线程池的执行流程如下图所示:
在这里插入图片描述

线程池的创建方式

Java线程池一共有7种,按创建类分为两种:

  • 通过Executors类创建
    • Executors.newFixedThreadPool创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待
    • Executors.newCachedThreadPool创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程
    • Executors.newSingleThreadExecutor创建单个线程数的线程池,它可以保证先进先出的执行顺序
    • Executors.newScheduledThreadPool创建一个可以执行延迟任务的线程池
    • Executors.newSingleThreadScheduledExecutor创建一个单线程的可以执行延迟任务的线程池
    • Executors.newWorkStealingPool创建一个抢占式执行的线程池(任务执行顺序不确定)JDK 1.8 中添加
  • 通过ThreadPoolExecutor类创建最原始的创建线程池的方式,它包含了 7 个参数可供设置,后面会详细讲。
    一般使用Java提供了创建线程池的接口Executor(),推荐用子类ThreadPoolExecutor来创建线程池。这在阿里巴巴《Java开发手册》中有说明:

【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:允许的请求队列长度Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

通过ThreadPoolExecutor创建

该类参数最多的构造方法如下:

public ThreadPoolExecutor(int corePoolSize,
                         int maximumPoolSize,
                         long keepAliveTime,
                         TimeUnit unit,
                         BlockingQueue<Runnable> workQueue,
                         ThreadFactory threadFactory,
                         RejectedExecutionHandler handler

参数:
corePoolSize - 池中所保存的线程数,包括空闲线程。
maximumPoolSize - 池中允许的最大线程数。
keepAliveTime - 在线程池中,核心线程即使在无任务的情况下也不能被清除,其余的线程都是有存活时间的,这里就是非核心线程可以保留的最长的空闲时间
unit - keepAliveTime 参数的时间单位。
workQueue - 执行前用于保持任务的队列,即阻塞(等待)队列。一共有ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue等7种阻塞队列Runnable 任务。
threadFactory - 执行程序创建新线程时使用的工厂,主要用来创建线程,默认为正常优先级、非守护线程。
handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。处理程序共有以下四种:

AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满
DisCardPolicy
不执行新任务,也不抛出异常
DisCardOldSetPolicy
将消息队列中的第一个任务替换为当前新进来的任务执行
CallerRunsPolicy
直接调用execute来执行当前任务

常用方法的相关信息

execute

public void execute(Runnable command)
在将来某个时间执行给定任务。可以在新线程中或者在现有池线程中执行该任务。 如果无法将任务提交执行,或者因为此执行程序已关闭,或者因为已达到其容量,则该任务由当前 RejectedExecutionHandler 处理。
参数:
command - 要执行的任务。
抛出:
RejectedExecutionException - 如果无法接收要执行的任务,则由 RejectedExecutionHandler 决定是否抛出 RejectedExecutionException
NullPointerException - 如果命令为 null

shutdown

public void shutdown()
按过去执行已提交任务的顺序发起一个有序的关闭,但是不接受新任务。如果已经关闭,则调用没有其他作用。
抛出:
SecurityException - 如果安全管理器存在并且关闭此 ExecutorService 可能操作某些不允许调用者修改的线程(因为它没有 RuntimePermission(“modifyThread”)),或者安全管理器的 checkAccess 方法拒绝访问。

shutdownNow

public List shutdownNow()
尝试停止所有的活动执行任务、暂停等待任务的处理,并返回等待执行的任务列表。在从此方法返回的任务队列中排空(移除)这些任务。
并不保证能够停止正在处理的活动执行任务,但是会尽力尝试。 此实现通过 Thread.interrupt() 取消任务,所以无法响应中断的任何任务可能永远无法终止。
返回:
从未开始执行的任务的列表。
抛出:
SecurityException - 如果安全管理器存在并且关闭此 ExecutorService 可能操作某些不允许调用者修改的线程(因为它没有 RuntimePermission(“modifyThread”)),或者安全管理器的 checkAccess 方法拒绝访问。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值