欢迎光临散文网 会员登陆 & 注册

Java 多线程

2023-03-06 12:03 作者:回到唐朝当少爷  | 我要投稿

多线程

  • 进程:程序的基本执行实体

  • 线程:操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位 简单理解:应用软件中相互独立,可以同时运行的功能

有了多线程,我们就可以让程序同时做多件事情,提高效率

  • 并发:同一时刻有,多个指令在单个CPU上交替执行

  • 并行:在同一时刻,多个指令在多个CPU上同时执行

多线程的实现方式

继承Thread类的方式

  1. 自己定义一个类继承Thread

  2. 重写run方法

  3. 创建子类的对象,并启动线程

实现Runnable接口的方式

  1. 自己定义一个类实现Runnable接口

  2. 重写里面的run方法

  3. 创建自己的类的对象

  4. 创建一个Thread类的对象并开启线程

利用Callable接口和Future接口

前两种方法都没有返回值,如果想获取多线程运行的结果:

  1. 创建一个类实现Callable接口

  2. 重写call(是有返回值的,表示多线程运行的结果)

  3. 创建MyCallable的对象(表示多线程要执行的任务)

  4. 创建FutureTask的对象(作用为管理多线程运行的结果)

  5. 创建Thread类的对象,并启动(表示线程)

三种方式对比

常见的成员方法

细节:

  • 如果我们没有给线程设置名字,其默认名字为Thread-X(序号从0开始)

  • 如果要给线程设置名字,可以用set方法进行设置,也可以用构造方法

  • 当JVM虚拟机启动时候,会自动地启动多条线程,其中有一条线程就叫做main线程,作用就是去调用main方法,并执行里面的代码.

  • sleep方法哪条线程执行到了,哪条线程就会在这里停留对应的时间,时间到了之后就会自动醒来,继续执行下面的其他代码

Java采用抢占式调度的方法,也就是随机

最小优先级为1,最大为10,默认为5,优先级越高抢到CPU的概率越高

  • 守护线程: 当其他非守护线程执行完毕后,守护线程会陆续结束 应用场景:如QQ聊天窗口与传文件,如果聊天窗口关闭了传文件就没必要了,所以可以把传文件设为守护线程

  • yield方法可以让线程分配尽可能均匀一点,较少使用

  • join方法表示将该线程插到当前线程之前,该线程结束后才会继续执行当先线程

线程的生命周期

创建线程对象:新建 调用start()后:有执行资格,没有执行权,需要不停地抢CPU:就绪 抢到CPU的执行权:执行资格,没有执行权:运行(可能又被其他线程抢走CPU的执行权回到上一个状态 run()执行完毕:线程死亡,变成垃圾:死亡

sleep()或其他阻塞式方法:没有执行资格没有执行权,等到时间到进入就绪状态争抢CPU

所以:sleep方法睡眠时间到后不会立即执行下面的代码,只有抢到CPU后才能执行

线程的安全问题

需求:100张票3个窗口售票

上述代码运行的结果有问题:

  1. 会出现同一张票卖给多个人

  2. 会出现超出100张票,如101,102

原因:线程执行时有随机性,某个线程执行到一半就会被其他线程抢走,加入刚好在ticket++执行完被抢走,别的线程执行完ticket++再回到原线程时,sout打印的ticket就变了

同步代码块

把操作共享数据的代码锁起来

  • 特点1:锁默认打开,有一个线程进去了,锁自动关闭

  • 特点2:里面代码全部执行完毕,线程出来,锁自动打开

同步方法

就是把synchronized关键字加到方法上

格式:

  • 特点1:同步方法是锁住方法里面所有的代码

  • 特点2:锁对象不能自己制定

    • 非静态:this

    • 静态:当前类的字节码文件对象

StringBuilder与StringBuffer:后者每个方法都加了synchronized,是线程安全的

Lock锁

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰地表达如何枷锁和释放锁,JDK5以后提供了一个新的锁对象Lock

Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作 Lock中提供了获得锁和释放锁的方法 void lock()获得锁 void unlock()释放锁

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化 ReentrantLock的构造方法 ReentrantLock()创建一个ReentrantLock的实例

上述代码有问题,在19:break;行,假设线程1拿到了CPU的执行权,此时线程1调出了循环,没有执行22:lock.unlock();,此时程序不会结束,锁没有关上

正确写法:使用try{}catch(){}finally{}

死锁

死锁是一个错误,我们应该避免发生

千万不要让两个锁嵌套起来

生产者和消费者(等待唤醒机制)

生产者和消费者是一个十分经典的多线程协作的模式

生产者:生产数据 消费者:消费数据

常见方法:

阻塞队列

继承结构

接口:Iterable,Collection,Queue,BlockingQueue 实现类:

  • ArrayBlockingQueue(底层是数组,有界)

  • LinkedBlockingQueue(底层是链表,无界但不是真正的无界,最大值为int的最大值)

线程的六种状态

新建状态(new)全部代码运行完毕

线程池

核心原理

  1. 创建一个池子,池子中是空的

  2. 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可

  3. 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待

代码实现:

  1. 创建线程池

  2. 提交任务

  3. 所有任务全部执行完毕,关闭线程池

Executors:线程池的工具类,通过调用方法返回不同类型的线程池对象

自定义线程池

线程池多大合适?

  • CPU密集型运算:最大并行数+1 +1的目的是当前面线程出问题时可以顶上去

  • IO密集型运算:最大并行数$\times$期望CPU利用率$\times\frac{总时间(CPU计算时间+等待时间)}{CPU计算时间}$

4核8线程最大并行数为8

多线程的额外扩展

见阿玮老师的多线程(额外扩展).md,含乐观锁,悲观锁,volatile关键字,原子性,一些并发工具类

Java 多线程的评论 (共 条)

分享到微博请遵守国家法律