多线程篇1—《多线程基础、线程池、线程通信》

本文详细介绍了多线程的基础概念,包括进程、线程的生命周期,以及并行与并发的区分。进一步讲解了线程的创建方式,如继承Thread类、实现Runnable接口和Callable接口,并探讨了线程的API,如线程的命名、守护线程、优先级等。接着深入探讨了线程池,包括不同类型的线程池创建和工作原理,以及线程池的生命周期管理。文章最后讨论了线程间的通信机制,如互斥同步、线程协作和中断,以及如何通过锁和等待/通知机制实现线程间的协调。
摘要由CSDN通过智能技术生成

参考资料:


线程池之ThreadPoolExecutor概述
一文读懂多线程
java3y

一、基础概念

1、进程

  • 在windows下打开任务管理器,可以发现我们在操作系统上运行的程序都是进程:
  • image-20210428132857359
  • 进程的定义:
    • 进程是程序的一次运行。
    • 进程是具有独立功能的程序在一个数据集合上运行的过程。
    • 进程是系统进行资源分配和调度的独立单位。每个进程都有自己的内存空间和系统资源。

2、线程

  • 为使程序能并发进行,系统必须进行一系列操作:
    • 创建进程:
      • 系统在创建一个进程时,必须为它分配其所必须的、除虚拟机以外的所有资源,如内存空间、I/O设备,以及建立相应的PCB;
    • 撤销进程:
      • 系统在撤销进程时,必须先对其所占有的资源进行回收操作,然后再撤销PCB。
    • 进程切换:
      • 对进程进行上下文切换时,需要保存当前进程的CPU环境,设置新选中进程的CPU环境,因此需花费不少处理时间。
  • image-20210428173527334
  • 引入线程主要是**为了提高系统的执行效率,减少处理机的空转时间和调度切换的时间,以及便于系统管理。**使OS具有更好的并发性
  • 简单来说:进程实现多处理非常耗费CPU的资源,而我们引入线程是作为调度和分派的基本单位(取代进程的部分基本功能调度)。

3、进程线程总结

  • 进程作为资源分配的基本单位

  • 线程作为资源调度的基本单位,是程序的执行单元,执行路径(单线程:一条执行路径,多线程:多条执行路径)。是程序使用CPU的最基本单位。

  • 线程有3个基本状态

    • 执行、就绪、阻塞
  • 线程有5种基本操作

    • 派生、阻塞、激活、 调度、 结束
  • 线程的属性:

    • 1)轻型实体;
    • 2)独立调度和分派的基本单位;
    • 3)可并发执行;
    • 4)共享进程资源。
  • 线程有两个基本类型

      1. 用户级线程
      • 管理过程全部由用户程序完成,操作系统内核心只对进程进行管理。
      1. 系统级线程(核心级线程):
      • 由操作系统内核进行管理
      • 操作系统内核给应用程序提供相应的系统调用和应用程序接口API,以使用户程序可以创建、执行以及撤消线程。
  • 值得注意的是:

    • 多线程的存在,不是提高程序的执行速度。
    • 其实是为了提高应用程序的使用率,程序的执行其实都是在抢CPU的资源,CPU的执行权。
    • 多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多,就会有更高的几率抢到CPU的执行权

4、并行与并发

  • 并行:
    • 并行性是指同一时刻内发生两个或多个事件。
    • 并行是在不同实体上的多个事件。
  • 并发:
    • 并发性是指同一时间间隔内发生的两个或多个事件。
    • 并发是在同一实体上的多个事件。
  • 由此可见:并行是针对进程的,并发是针对线程的

5、线程生命周期

  • 线程的生命周期可以通过getstate()获得,Thread.state类型分为:

    • 创建new、可运行runnable、阻塞Blocking、等待Waiting、死亡Terminaled
  • 20210106153631

    20210311182715

(1)新建(new)

  • 新建状态,创建了线程对象,在Start启动前的状态
  • 创建后尚未启动

(2)可运行(runnable)

  • 可能正常运行,也可能正在等待CPU时间片

  • 包含了操作系统线程状态中的Running和Ready。

    • Ready状态,表示该线程可以被资源调度器进行调度。
    • Running状态,表示该线程正在执行,
  • 如果用yieid方法可以把RUNNING状态转化为READY状态

(3)阻塞(Blocking)

  • 等待获取一个排他锁,如果其他线程释放了锁就会结束此状态,
  • 线程执行了wait()、thread.join() 方法会把线程转化为Waiting等待状态,
  • 执行object.notify()方法,或者加入的线程执行完毕,当前线程会转化为RUNNABLE状态。

(4)无限期等待(Waiting)

等待其他线程显式的唤醒,否则不会被分配CPU时间片。

进入方法 退出方法
没有设置Timeout参数的Object.wait()方法 Object.notify()/Object.notifyAll()
没有设置Timeout参数的Object.join()方法 被调用的线程执行完毕
LockSupport.park()方法 -

(5)限期等待Timed waiting

  • 无需等待其他线程显式的唤醒,在一定时间后会被系统自动唤醒。

  • 调用Thread.sleep( ) 方法使线程进入限期等待状态时,常常用“使一个线程睡眠”进行描述。

  • 调用Thread.wait()方法时线程进入限期等待或者无限等待时,常常用“挂起一个线程”进行描述。

  • 睡眠和挂起是用来描述行为,而阻塞和等待时描述状态。

  • 阻塞和等待的区别在于,阻塞式被动的,它是在等待获取一个排他锁,而等待是被动的,通过调用Thread.sleep()和Object.wait()等方法进入。

进入方法 退出方法
Thread.sleep() 时间结束
设置了Timeout参数的Object.wait()方法 时间结束/Object.notify()/Object.notifyAll()
设置了Timeout参数的Object.join()方法 时间结束/被调用的线程执行完毕
LockSupport.parkNanos()方法 -
LockSupport.parkUtil()方法 -

(6)死亡Terminaled

线程结束任务之后自己结束,或者产生了异常自己结束。

6、死锁

(1)概念

  • 死锁是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
  • 多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
  • 线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。

(2)死锁必要条件

  • 互斥条件:
    • 在一段时间内某资源只由一个进程占用。
    • 如果此时还有其它进程请求资源,就只能等待,直至占有资源的进程用毕释放。
  • 占有且等待条件:
    • 指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
  • 不可抢占条件:
    • 别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
  • 循环等待条件:
    • 若干进程之间形成一种头尾相接的循环等待资源关系。(比如一个进程集合,A在等B,B在等C,C在等A)

(3)避免死锁

1、避免一个线程同时获得多个锁
2、避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
3、尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制

二、多线程基础

1、Thread类的顶部

  • jvm允许多个线程并发执行
  • 线程有优先级,优先级高的先执行
  • 线程可能会有守护线程(后台线程)
  • 初始化的时候,优先级平等
  • jvm启动时,main线程先启动,无守护线程
  • 线程的结束有两种情况:
    • 1、执行退出exit方法
    • 2、run方法执行完毕或者抛出了异常
  • 两个方法创建新的线程
    • 继承thread类,重写run方法
    • 实现Runnable方法,重写run方法
    • 实现Callable接口,重写run方法
  • 每个线程都有自己的名字,如果没有,默认指定一个。
  • aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAxOC80LzE3LzE2MmQxOTdiNzI4MDhjNDM

2、继承Thread类

  • 创建:

    • 继承Thread类,重写里面的Run方法,因为Thread类也实现了Runnable接口。
  • 启动:

    • 创建子类对象,调用start方法
  • 缺点:

    • 不建议使用,继承了一个类,就不能继承其他父类了
  • code:

public class MyThread extends Thread {
   

	@Override
	public void run() {
   
		for (int x = 0; x < 200; x++) {
   
			System.out.println(x);
		}
	}
    
    public static void main(String[] args) {
   
		// 创建两个线程对象
		MyThread my1 = new MyThread();
		MyThread my2 = new MyThread();

		my1.start();
		my2.start();
	}

}

3、实现Runnable接口

  • 创建:

    • 实现Runnable接口 重写Run方法
  • 启动:

    • 创建实现类对象、Thread对象 调用Start方法
  • 推荐:

    • 避免单继承的局限性,优先使用接口
  • code:

public class MyRunnable implements Runnable {
   

	@Override
	public void run() {
   
		for (int x = 0; x < 100; x++) {
   
			System.out.println(x);
		}
	}
	public static void main(String[] args) {
   
		// 创建MyRunnable类的对象
		MyRunnable my = new MyRunnable();

		Thread t1 = new Thread(my);
		Thread t2 = new Thread(my);

		t1.start();
		t2.start();
	}
}

demo:

建立三个用户模仿抢票:

public class RabbitClass extends RunnableThread {
   
    private  int num=99;
    @Override
    public void run() {
   
        while (true) {
   
            if(num<0)
                break;
            try {
   
                Thread.sleep(200);//模拟延时
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+num--);
        }
    }
    public static void main(String[] args) {
   
        RabbitClass rabbitClass=new RabbitClass();
        new Thread(rabbitClass,"one").start();//用户一
        new Thread(rabbitClass,"two").start();//用户二
        new Thread(rabbitClass,"three").start();//用户三
    }
}

4、实现Callable接口

与Runnble相比,Callable可以有返回值,返回值通过FutureTask进行封装。

public class MyCallable implements Callable {
   
    @Override
    public Integer call() throws Exception {
   
        return 123;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
   
        MyCallable callable = new MyCallable();
        /**
         * Future 是获取异步计算结果,对任务执行的结果进行获取
         * FutureTask 除了实现了Future接口外还实现了Runnable接口
         */
        FutureTask<Integer> ft = new FutureTask<>(callable);
        Thread thread = new Thread();
        thread.start();
        System.out.println(ft.get());
    }
}
  • 补充:
    • Callable就是Runnable的扩展
    • Runnable没有返回值,不能抛出受检查的异常,而Callable可以
    • Future一般我们认为是Callable的返回值,但他其实代表的是任务的生命周期(当然了,它是能获取得到Callable的返回值的)
    • image-20210429164139464

5、实现多线程注意细节

(1)run()和start()方法区别:

  • run():仅仅是封装被线程执行的代码,直接调用是普通方法
  • start():首先启动了线程,然后再由jvm去调用该线程的run()方法。

(2)jvm虚拟机的启动是单线程的还是多线程的?

  • 是多线程的。不仅仅是启动main线程,还至少会启动垃圾回收线程的,不然谁帮你回收不用的内存~

6、Thread线程类API

image-20210429152947865

(1)setName()/getName()

  • Thread.setName()/getName()
  • 获取当前线程名Thread.currentThread().getName()
  • new Thread(myThread, “关注公众号Java3y”)
  • 创建线程时没有设置线程名会给个默认明
  • 通过设置线程名称有助于程序调试,提高可读性,建议为每一个线程设置一个可以体现线程功能的名称。

(2)Daemon守护线程

  • 守护线程是为其他线程服务的

    • 垃圾回收线程就是守护线程
  • 守护线程特点

    • 当别的用户线程执行完了,虚拟机就会退出,守护线程也就会被停止掉了。
    • 也就是说:守护线程作为一个服务线程,没有服务对象就没有必要继续运行
  • 注意点:

    • 在线程启动前设置为守护线程,方法是setDaemon(boolean on)
    • 使用守护线程不要访问共享资源(数据库、文件等),因为它可能会在任何时候就挂掉了。
    • 守护线程中产生的新线程也是守护线程
  • main()属于非守护进程。

  • 设置守护线程。

  • public static void main(String[] args) {
         
        Thread thread = new T
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值