Java——多线程

一、什么是多线程

  • 主要优点:
    充分利用CPU空闲时间片,用尽可能短的时间完成用户的请求。也就是使程序的响应速度更快 。
  • 应用场景:
    • 多任务处理。多个用户请求服务器,服务端程序可以开启多个线程分别处理每个用户的请求,互不影响。
    • 单个大任务处理。下载一个大文件,可以开启多个线程一起下载,减少整体下载时间。

① 并发与并行

并行: 指多个事件任务(同时发生)。
并发: 指在同一个时刻只能有一条指令执行,但多个进程的指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。程序并发执行可以在有限条件下,充分利用CPU资源,

② 线程与进程

进程: 是操作系统调度和分配的最小单位。是对一个程序运行过程(创建-运行-消亡)的描述,系统会为每个运行的程序建立一个进程,并为进程分配独立的系统资源,比如内存空间等资源。

在这里插入图片描述

线程: 线程是CPU调度的最小单位。是进程中的一个执行单元,负责完成执行当前程序的任务,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这时这个应用程序也可以称之为多线程程序。多线程使得程序可以并发执行,充分利用CPU资源。
QQ 打开多个消息框 和 百度网盘同时下载多个资源等都是都多线程

在这里插入图片描述

总结:

不同的进程之间是不共享内存的。不同的线程是共享同一个进程的内存的。不同的线程也有自己独立的内存空间。对于方法区,堆中中的同一个对象的内存,线程之间是可以共享的,但是栈的局部变量永远是独立的。

③ 线程调度规则

指CPU资源如何分配给不同的线程。Java采用的是抢占式调度方式。常见的两种线程调度方式:

  • 分时调度
    所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
  • 抢占式调度
    优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),

二、线程的创建与启动

java虚拟机是支持多线程的,当运行Java程序时,至少已经有一个线程了,那就是main线程。
Java 中创建线程的两种方式:

  1. 继承 Thread 类
  2. 实现 Runnable 接口

① 继承Thread类

Java中 java.lang.Thread是表示线程的类,每个Thread类或其子类的实例代表一个线程对象。

② 实现Runnable接口

Java有单继承的限制,当我们无法继承 Thread 类时,那么该如何做呢?在核心类库中提供了Runnable接口,我们可以实现Runnable接口,重写run()方法,然后再通过Thread类的对象代理启动和执行我们的线程体run()方法

③ 两种创建线程方式比较

Thread类本身也是实现了Runnable接口的,run方法都来自Runnable接口,run方法也是真正要执行的线程任务。

  • 因为Java类是单继承的,所以继承Thread的方式有单继承的局限性,但是使用上更简单一些。
  • 实现Runnable接口的方式,避免了单继承的局限性,并且可以使多个线程对象共享一个Runnable实现类(线程任务类)对象,从而方便在多线程任务执行时共享数据。

三、Thread类

① 线程使用常用方法

方法名称方法描述
public void run()此线程要执行的任务在此处定义代码。
public String getName()获取当前线程名称。
public static Thread currentThread()返回对当前正在执行的线程对象的引用。
public final boolean isAlive()测试线程是否处于活动状态。如果线程已经启动且尚未终止,则为活动状态。
public final int getPriority()返回线程优先级
public final void setPriority(int newPriority)改变线程的优先级

② 线程控制常见方法

方法名称方法描述
public void start()导致此线程开始执行; Java虚拟机调用此线程的run方法。
public static void sleep(long millis)线程睡眠,使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)
public static void yield()线程礼让,当前线程失去执行权,让系统的线程调度器重新调度一次。可能还是调用它
void join()加入线程,当前线程中加入一个新线程,等待加入的线程终止后再继续执行当前线程。
void join(long millis)等待该线程终止的时间最长为 millis 毫秒。如果millis时间到,将不再等待。
void join(long millis, int nanos)等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
public final void stop()强迫线程停止执行。 该方法具有不安全性,已被弃用,最好不要使用。
public void interrupt()中断线程,实际上是给线程打上一个中断的标记,并不会真正使线程停止执行。
public static boolean interrupted()检查线程的中断状态,调用此方法会清除中断状态(标记)。
public boolean isInterrupted()检查线程中断状态,不会清除中断状态(标记)
public void setDaemon(boolean on)将线程设置为守护线程。必须在线程启动之前设置,否则会报IllegalThreadStateException异常。
public boolean isDaemon()检查当前线程是否为守护线程。

③ 线程优先级

每个线程都有一定的优先级,优先级高的线程将获得较多的执行机会。
每个线程默认的优先级都与创建它的父线程具有相同的优先级。
Thread类提供了setPriority(int newPriority)和getPriority()方法类设置和获取线程的优先级,其中setPriority方法需要一个整数,并且范围在[1,10]之间,通常推荐设置Thread类的三个优先级常量:

  • MAX_PRIORITY(10):最高优先级
  • MIN _PRIORITY (1):最低优先级
  • NORM_PRIORITY (5):普通优先级,默认情况下main线程具有普通优先级。

④ 守护线程

守护线程,主要为其他线程服务,当程序中没有非守护线程执行时,守护线程也将终止执行。JVM垃圾回收器也是守护线程。

四、线程生命周期

① 线程的声明周期

CPU需要在多条线程之间切换,于是线程状态会多次在运行、阻塞、就绪之间切换
线程的生命周期描述为五种状态:
新建(New)就绪(Runnable)运行(Running)阻塞(Blocked)死亡(Dead)

  1. 新建
    当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状。此时它和其他Java对象一样,仅仅由JVM为其分配了内存,并初始化了实例变量的值。此时的线程对象并没有任何线程的动态特征,程序也不会执行它的线程体run()。

  2. 就绪
    但是当线程对象调用了start()方法之后,线程就从新建状态转为就绪状态。这时线程并未执行,只是具备了运行的条件,还需要获取CPU资源后才能执行。

  3. 运行
    如果处于就绪状态的线程获得了CPU资源,开始执行run()方法的线程体代码,则该线程处于运行状态。如果计算机只有一个CPU,在任何时刻只有一个线程处于运行状态,如果计算机有多个处理器,将会有多个线程并行(Parallel)执行。
    对于抢占式策略的系统而言,系统会给每个可执行的线程一个小时间段来处理任务,当该时间用完,系统会剥夺该线程所占用的资源,让其回到就绪状态等待下一次被调度。此时其他线程将获得执行机会,而在选择下一个线程时,系统会适当考虑线程的优先级。

  4. 阻塞
    当在运行过程中的线程遇到某些特殊情况时,线程会临时放弃CPU资源,不再执行,即进入阻塞状态。比如:线程调用了sleep()方法,会主动放弃所占用的CPU资源。

  5. 死亡
    线程完成任务结束或意外终止后,线程就处于死亡状态。

② JDK定义的六种线程状态

Java 中没有区分就绪运行状态,而是定义成了一种状态Runnable
传统模型中的阻塞状态在枚举类的定义中又细分为了三种状态的:BLOCKEDWAITINGTIMED_WAITING

  • BLOCKED:是指互有竞争关系的几个线程,其中一个线程占有锁对象时,其他线程只能等待锁。只有获得锁对象的线程才能有执行机会。

  • TIMED_WAITING:当前线程执行过程中遇到Thread类的sleep或join,Object类的wait,LockSupport类的park方法,并且在调用这些方法时,设置了时间,那么当前线程会进入TIMED_WAITING,直到时间到,或被中断。

  • WAITING:当前线程执行过程中遇到遇到Object类的wait,Thread类的join,LockSupport类的park方法,并且在调用这些方法时,没有指定时间,那么当前线程会进入WAITING状态,直到被唤醒。

    • 通过Object类的wait进入WAITING状态的要有Object的notify/notifyAll唤醒;

    • 通过Condition的await进入WAITING状态的要有Conditon的signal方法唤醒;

    • 通过LockSupport类的park方法进入WAITING状态的要有LockSupport类的unpark方法唤醒

    • 通过Thread类的join进入WAITING状态,只有调用join方法的线程对象结束才能让当前线程恢复;

说明:当从WAITINGTIMED_WAITING恢复到Runnable状态时,如果发现当前线程没有得到监视器锁,那么会立刻转入BLOCKED状态。

五、线程安全

当我们使用多个线程访问同一资源(可以是同一个变量、同一个文件、同一条记录等)的时候,但是如果多个线程中对资源有读和写的操作,就会出现前后数据不一致问题,这就是线程安全问题。
总结: 多线程下多条语句操作共享数据

① 使用同步解决线程安全

Java中常使用关键字 synchronized 来实现同步机制:

同步方法:synchronized 关键字直接修饰方法,表示同一时刻只有一个线程能进入这个方法,其他线程在外面等着。

同步代码块:synchronized 关键字可以用于某个区块前面,表示只对这个区块的资源实行互斥访问。

② 锁对象选择

  • 锁对象可以是任意类型。
  • 多个线程对象 要使用同一把锁。

③ 同步代码块的锁对象

  • 静态代码块中:使用当前类的Class对象
  • 非静代码块中:习惯上先考虑this,但是要注意是否同一个this

六、线程间通信

为什么要处理线程间通信:
多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。而多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些通信机制,可以协调它们的工作,以此来帮我们达到多线程共同操作一份数据。

比如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,此时B线程必须等到A线程完成后才能执行,那么线程A与线程B之间就需要线程通信

① 等待唤醒机制

什么是等待唤醒机制 ?
线程协作(等待唤醒机制)就是在一个线程满足某个条件时,就进入等待状态(wait()/wait(time)), 等待其他线程执行完他们的指定代码过后再将其唤醒(notify());或可以指定wait的时间,等时间到了自动唤醒;在有多个线程进行等待时,如果需要,可以使用 notifyAll()来唤醒所有的等待线程。wait/notify 就是线程间的一种协作机制。

被通知线程被唤醒后也不一定能立即恢复执行,需要重写获取锁
如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE(可运行) 状态;
否则,线程就从 WAITING 状态又变成 BLOCKED(等待锁) 状态

调用wait和notify方法需要注意的细节

  1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
  2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
  3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用,并且必须要通过锁对象调用这2个方法。

七、释放锁操作与死锁

任何线程进入同步代码块、同步方法之前,必须先获得对同步监视器的锁定,那么何时会释放对同步监视器的锁定呢?

① 释放锁的操作

当前线程的同步方法、同步代码块执行结束。
当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致当前线程异常结束。
当前线程在同步代码块、同步方法中执行了锁对象的wait()方法,当前线程被挂起,并释放锁。

② 不会释放锁的操作

线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行。
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该该线程挂起,该线程不会释放锁(同步监视器)。应尽量避免使用suspend()和resume()这样的过时来控制线程。

③ 死锁

不同的线程分别锁住对方需要的同步监视器对象不释放,都在等待对方先放弃时就形成了线程的死锁。一旦出现死锁,整个程序既不会发生异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值