Java多线程

Java多线程

    • 前言
    • 进程与线程
      • 进程概念
      • 线程概念
      • 为什么要在代码中引入多线程?
      • 并发编程及优缺点
      • 并发编程的三要素
      • 并发、并行、串行的区别
      • 线程与进程的区别
      • 上下文切换
      • 守护线程和用户线程的区别
      • 用户态和内核态
    • 创建线程的几种方式
      • Thread类常用方法
      • Runnable 和 Callable的比较
      • 线程的run()和start()的区别
      • 为什么调用start()方法时会执行run()方法,而不是直接调用run()
      • Callable和Future
      • FutureTask
    • 线程的状态和基本操作
      • 线程的生命周期和五种基本状态
      • java中线程的调度策略
      • sleep()和wait()有什么区别?
      • 如何停止一个处在运行态的线程
      • 系统创建的线程过多会有什么影响

前言

本篇博客将根据现有知识对Java多线程做以小结,以下博客仅作为个人学习过程的小结,如能对各位博友有所帮助不胜荣幸。
本篇博客将简单介绍计算机线程与进程的概念,以及创建线程的方法,重点叙述了线程的生命周期及其状态,后期随学习深入还会补充修改。

进程与线程

进程概念

进程即一段程序的执行过程,是计算机中的程序关于某数据集合上的一次运行活动,是系统分配资源的最小单位

线程概念

线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位,一个进程可以有很多线程,每条线程并行执行不同的任务

为什么要在代码中引入多线程?

  1. 可以使用多个线程同步处理进程任务,从而提高效率
  2. 如果阻塞点过多,一个线程可能会处于长期阻塞等待而无法处理后续任务,如当TCP服务器在等待连接、IO在等待输入时,这些情况都会导致该线程阻塞等待,而此时可以使用其他线程去先处理接下来的任务,从而提高效率

并发编程及优缺点

并发编程:是指在一台处理器上“同时”处理多个任务。并发是在同一实体上的多个事件。多个事件在同一时间间隔发生

并发充分利用多核CPU的计算能力,通过并发编程的形式可以将多核CPU的计算能力发挥到极致,大大的提高性能,并且方便业务的拆分,提高系统的并发能力和性能;面对复杂的业务模型,并发程序会比串行程序更适合业务需求

虽然并发编程的目的是为了提高程序的执行效率,但有时还会遇到各种问题如:内存泄漏、频繁上下文切换、死锁的问题从而导致效率降低

并发编程的三要素

  • 原子性:指一个或多个操作要么全部执行成功,要么全部执行失败
  • 可见性:一个线程对共享变量的修改,另一个线程能够立刻看到
  • 有序性:程序执行的顺序按照代码的先后顺序执行(处理器可能会对指令进行重排序)

解决多线程安全的办法:

  1. synchronized、Lock可以解决原子性问题
  2. volatile、Lock可以解决可见性和有序性问题

并发、并行、串行的区别

  • 并发:多个任务在同一个CPU上执行,系统按时间片轮转调度使得这些任务交替轮流执行,从宏观上看这些任务是同时执行的
  • 并行:同一个时间点,多个处理器或多核处理器同时处理多个任务,无论宏观还是微观都是同时执行
  • 串行:多个任务按照顺序依次执行,前一个任务执行完后一个任务开始执行

线程与进程的区别

  • 根本区别:进程是操作系统资源分配的最小单位,而线程是处理任务调度和执行的最小单位
  • 资源开销:每个进程都有单独的代码和数据空间,进程之间的切换会有较大的开销;线程可以开做轻量级的进程,同一类线程共享代码和数据空间,每个线程有自己独立的运行栈和程序计数器,线程之间的切换开销较小
  • 包含关系:一个进程可以包含多个线程,这些线程可以共享同步资源
  • 内存分配:同一个进程中的所有线程共享本进程的地址空间和资源,而进程之间的地址空间和资源相互独立
  • 影响关系:一个进程崩溃后,不会影响其他进程;而一个线程崩溃后其他线程也会收到影响,从而整个进程崩溃
  • 执行过程:每个独立的进程都有程序运行的入口,顺序执行序列和出口,但线程不能独立执行,必须依存于进程

上下文切换

当前任务在执行完CPU的时间片后切换到另一个任务之前会先保存自己的状态,以便下次时间片轮转再次回到这个任务的时候,可以再次加载这个任务的状态。任务从保存到在加载的过程就是一次上下文切换

守护线程和用户线程的区别

  • 用户线程:运行在前台,执行具体的任务,如程序的主线程等都是用户线程
  • 守护线程:运行在后台,为其他前台线程服务,一旦所有用户线程结束运行,守护线程也会结束

最本质的区别:用户线程结束,JVM退出,不管这个时候还有没有守护线程运行。守护线程不会影响JVM的推出

用户态和内核态

内核态:涉及到安全相关的指令,权限要求比较高,通过系统接口调用
用户态:开发给用户程序可以直接让某设备执行的操作,不存在安全隐患

BIO:同步阻塞IO,执行到某行代码时挂起等待,系统内核再执行IO操作 用户程序(用户态 ===> 内核态) 如硬盘读取IO数据
NIO:同步非阻塞IO

创建线程的几种方式

  • 继承Thread类
  • 实现Runnable接口
  • 实现Callable接口
  • 使用Executors工具类创建线程池

1.继承自Thread类
(1) 定义一个Thread类的子类,重写run()方法,将相关操作实现,run()方法就是线程要执行的业务逻辑方法;
(2) 创建自定义的线程子类对象
(3) 调用子类实例的start()方法启动线程

class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"的run方法正在执行");
    }
}

class Main{
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.start();
        t2.start();
        System.out.println(Thread.currentThread().getName()+"的main方法正在执行");
    }
}

Java多线程_第1张图片
2.实现Runnable接口
(1)定义一个接口实现Runnable接口,并重写run()方法
(2)创建MyRunnable的实例myRunnable,以myRunnable为参数创建Thread对象
(3)调用线程对象的start()方法

class MyRunnable implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"的run方法正在执行");
    }
}

class Main{
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread t1 = new Thread(myRunnable);
        Thread t2 = new Thread(myRunnable);
        t1.start();
        t2.start();
        System.out.println(Thread.currentThread().getName()+"的main方法正在执行");
    }
}

在这里插入图片描述
3.实现Callable接口
(1)创建实现Callable接口的类MyCallable
(2)以MyCallable类的实例为参数创建FutureTask对象
(3)将FutureTake对象作为参数创建Thread对象
(4)调用线程对象的start()方法

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

class MyCallable implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"的call方法正在执行");
        return 1;
    }
}

class Main{
    public static void main(String[] args) {
        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> futureTask1 = new FutureTask<Integer>(new MyCallable());
        FutureTask<Integer> futureTask2 = new FutureTask<Integer>(new MyCallable());
        Thread t1 = new Thread(futureTask1);
        Thread t2 = new Thread(futureTask2);
        t1.start();
        t2.start();
        
        System.out.println(Thread.currentThread().getName()+"的main方法执行完成");
    }
}

Java多线程_第2张图片
4.使用Executors工具类创建线程池
Executors提供了一系列工厂方法用于创建线程池,返回的线程池都实现了ExecutorService接口

主要有 newFixedThreadPoll、newCachedThreadPool、newSingleThreadExecutor、newScheduleThreadPool这四种线程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class MRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"run()方法执行中");
    }
}

class Main{
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        MyRunnable myRunnable = new MyRunnable();
        for(int i = 0; i < 5; i++){
            executorService.execute(myRunnable);
        }
        executorService.shutdown();
    }
}

Java多线程_第3张图片

Thread类常用方法

实例方法
Java多线程_第4张图片

静态方法
Java多线程_第5张图片

Runnable 和 Callable的比较

相同点:

  • 都是接口
  • 都可以编写多线程代码
  • 都采用Thread.start()启动线程

不同点:

  • Runnable接口的run()方法没有返回值;Callable接口的run()方法有返回值,是泛型,和FutureTask配合可以用来获取异步执行的结果
  • Runnable接口run()方法只能抛出运行时异常,且无法捕获处理;Callable接口call()方法允许抛出异常,可以获取异常信息

线程的run()和start()的区别

  • start()方法用于启动线程,run()方法用于执行线程运行时的代码。run()方法可以重复调用,但start()方法只能调用一次
  • start()方法用于启动线程,真正实现了多线程的运行。调用start()方法时无需等待此时正在执行的run()方法方法体代码执行完毕;此时线程进入就绪态,并没有开始运行,一直等待系统分配时间片,调用run()方法,此时该线程进入运行态,当run()方法结束,该线程终止
  • run()方法是在本线程里的,只是线程的一个函数。如果直接调用run()方法,就相当于在当前线程调用了一个普通的函数;路径还是一条,并没有运行多一个线程。
    所以在线程启动时,要调用start()方法而不是run()方法

为什么调用start()方法时会执行run()方法,而不是直接调用run()

多线程工作的流程:new一个Thread类,该线程被创建出来——调用start()方法,该线程进入就绪态——当该线程分配到时间片后进入运行态——此时线程自动调用run()方法执行其中的内容,由此实现多线程工作

如果调用run()方法,会把run()方法当成是一个main线程下的普通方法区执行,并没有另开辟一个线程来运行,本质上还是单线程运行

Callable和Future

Callable接口类似于Runnable接口,但Runnable接口的run()方法不会反悔结果,并且无法抛出返回结果的异常;而Callable接口的功能更强大些,call()方法有返回值,这个返回值可以被Future拿到,并且可以抛出异常
Future接口表示异步任务,是一个可能还没有完成的异步任务的结果,Callable用于产生结果,Future用于获取结果

FutureTask

Future表示一个异步运算的任务。FutureTask里面可以传入一个Callable的具体实习类**,可以对这个异步运算的任务的结果进行等待获取**、判断是否已经完成、取消任务等操作。只有当运行完成的时候结果才能取回,如果运算尚未完成,get()方法将会阻塞。
一个FutureTack对象可以对调用了Callable和Runnable的对象进行包装,由于FutureTack也是Runnable接口的实现类,所以FutureTask也可以放入线程池中。

线程的状态和基本操作

线程的生命周期和五种基本状态

Java多线程_第6张图片

  • 创建(new):创建一个新的线程
  • 就绪态(runnable):当线程调用其start()方法时,线程进入就绪态态,向系统提出运行申请,等待分配时间片
  • 运行态(running) :当线程别系统选中,获取到时间片后,线程进入运行态,此时自动调用线程的run()方法
  • 阻塞态(block):由于某种原因,线程此时停止执行,进入阻塞态,持续等待直到被某个时机被重新调回就绪态

阻塞态可细分为三种:

1、等待阻塞:当处在运行态的线程执行wait()方法时,JVM就会将其放入等待队列中,线程进入等待阻塞状态,此时该线程会释放锁,直到被其他线程唤醒,重新进入同步队列,直到竞争锁成功重新进入就绪态
2、同步阻塞:当线程在获取synchronized同步锁失败,JVM会将其放入同步队列中,等待下次竞争锁,直到竞争成功进入就绪态
3、其他阻塞:当线程内部调用sleep()、join()或者发出IO请求时,线程会进入阻塞状态,直到sleep()设定的时间已过,join()等待线程销毁或者IO处理完成,此时线程重新进入就绪态,此过程全程不会释放锁

  • 销毁(dead):当该线程的run()方法执行完毕,或者main线程的mian()方法执行结束,或因为异常退出线程后,该线程生命周期结束被销毁

java中线程的调度策略

java虚拟机采用抢占式调度模型,值让优先级高的线程先分配CPU时间片,对于优先级相同的线程,采用随机分配,处于运行态的线程会一直执行,直到执行结束或被终止
终止线程的几种情况:

  1. 线程调用yield()方法让出对CPU的占用
  2. 线程调用sleep()方法时线程进入睡眠状态
  3. 线程由于IO操作被迫阻塞等待
  4. 另一个更高优先级的线程进入就绪态
  5. CPU一次分配的时间片执行完

sleep()和wait()有什么区别?

两者都可暂停当前线程

  • 所在类不同:sleep()时Thread类的静态方法,wait()是Object类的方法
  • 释放锁:sleep()不会释放锁,wait()会释放锁
  • 用途不同:wait()通常用于线程间通信,sleep()通常用于暂停线程
  • 结果不同:sleep()方法执行完成后,线程会再次进入就绪态;wait()方法被notify()唤醒后,线程会进入同步队列重新抢占锁

如何停止一个处在运行态的线程

  1. 该线程的run()方法执行结束后线程自动终止
  2. 使用stop()方法强制终止,但一般很少这么用
  3. 使用interrupt()方法中断线程(其流程为,设置一个中断标志位,调用interrupt()方法通知系统请求关闭线程,待系统在适合的时间自行中断)

interrupt()、interrupted()、isInterrupted()方法的区别

  • interrupt()方法和isInterrupted()方法都是实例方法,通过Thread对象调用;interrupted()则是静态方法,通过Thread类调用,三个方法都是与线程中断有关的
  • interrupt()方法:用来设置中断标志位,通知系统请求结束线程,由系统决定具体何时中断

此时如果线程在阻塞状态:
那么就会抛出InterruptedException异常,并重置中断标志位

如果线程不在阻塞状态:
使用Thread.interrupted()判断当前中断标志位是否被设置,并重置中断标志位
使用Thread.currentThread.isInterrupted()判断当前中断标志位是否被设置,不重置中断标志位

系统创建的线程过多会有什么影响

  • 线程的生命周期开销非常高
  • 消耗过多的CPU
  • 降低JVM的效率

以上便是对java多线程的知识点小结,随着后续学习的深入还会同步的对内容进行补充和修改,如能帮助到各位博友将不胜荣幸,敬请斧正

你可能感兴趣的:(java,多线程)