• 多线程学习


    并发:交替运行

    并行:一起运行

    多线程实现方式

    继承Thread类

    ①自己定义一个类继承Thread

    1. public class MyThread extends Thread{
    2. public void run(){
    3. }
    4. }

    ②重写run方法

    1. public class MyThread extends Thread{
    2. public void run(){
    3. "重写的内容"
    4. }
    5. }

    ③创建子类对象,并启动线程

    1. MyThread t1 = new MyThread();
    2. t1.setName("线程1的名字");
    3. t1.start("开始运行线程1");

    实现Runnable接口

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

    1. public class MyRun implements Runnable{
    2. public void run(){
    3. }
    4. }

    ②重写里面的run方法

    1. public class MyRun implements Runnable{
    2. public void run(){
    3. "重写的代码"
    4. }
    5. }

    注意:在重写run的过程中是不能调用getName这个方法的,因为使用的是自己创建的类,如果要使用的话,可以用Thread t = Thread.currentThread();就相当于返回的是相应线程的对象

    ③创建自己的类的对象

    Myrun mr = new Myrun();

    ④创建一个Thread类的对象

    Thread t1 = new Thread(mr);

    利用Callable接口和Future接口(可以获取到多线程运行的结果)

    ①创建一个类MyCallable实现Callable接口

    1. public class MyCallable implements Callable<V>{
    2. public V call(){
    3. return V;
    4. }
    5. }

    ②重写call(有返回值)

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

    MyCallable mc = new MyCallable();

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

    1. //管理Callanle
    2. FutureTask<V> ft = new FutureTask<>(mc);

    ⑤创建Thread类的对象,并启动

    1. Thread t1 = new Thread(ft);
    2. t1.start();
    3. V result = ft.get();//获取结果

    常见的成员方法

    setName()

    注意:

    • 如果没有给线程设置名字,线程也是有默认名字的
    • 如果我们要给线程设置名字,可以用set方法进行设置,也可以用构造方法设置

    currentsThread()

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

    sleep()

    • 哪条线程执行到这个方法,那么那条线程就要在这停留相应的时间
    • 方法的参数:就表示这个停留的时间,单位毫秒
    • 当时间到了之后,线程就会自动醒来,继续执行下面的代码

    setPriority(); -----设置优先级

    线程的调度(抢占CPU)

    抢占式调度:随机性

    非抢占式调度

    setDeamon()  -----  设置守护线程

    当其他线程都结束之后,守护线程也随之结束

    例子:如果在聊条过程中,一边发送信息,一边发送文件,当关闭了发送信息的窗口的时候,那么文件也没必要发送,就结束发送文件这个线程

    yield()  ------ 礼让逻辑

    作用,尽可能让各个线程执行的次数均匀

    join() ------- 加入线程

    线程的生命周期

    线程的安全

    同步代码块

    容易出现问题的原因:在线程执行的时候,有随机性

    解决:如果能把线程操作共享数据的代码锁起来,只让一条线程执行,这条线程执行完之后才能让下一条线程执行代码

    关键字:synchronized(锁对象){操作共享的数据}

    1. public class MyThread extends Thread{
    2. //static保证锁对象是唯一的
    3. //创建一个任意的锁对象
    4. static Object obj = new Object();
    5. synchronized(obj){
    6. 操作代码
    7. }
    8. }

    特点:

    • 锁默认打开,有一个线程进去了,锁自动关闭
    • 里面的代码全部执行完毕,线程出来,锁自动打开

    同步方法

    特点:

    • 同步方法是锁定方法里面的所有代码
    • 锁对象不能自己指定

    //Ctrl + Alt + m 将代码块抽取成方法

    Lock锁

    可以手动上锁和释放锁

    lock();上锁

    unlock();释放锁

    死锁(错误)

    不要让俩个锁嵌套使用

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

    消费者等待

    首先是消费者抢到了CPU的执行权

    消费者

    1. 判断是否生产者生产了数据
    2. 如果没有就等待(wait)

    生产者

    1. 生产数据
    2. 制作数据
    3. 叫醒消费者消费数据(notify)

    生产者等待

    俩次都是生产者抢到了CPU的执行权

    生产者

    1. 判断是否现在是否生成了数据
    2. 有:等待
    3. 没有:生产数据
    4. 把数据放着
    5. 叫消费者消费数据

    消费者

    1. 判断是否有数据可以消费
    2. 没有就等待
    3. 有就开始消费
    4. 没有就叫生产者开始生产

    常用方法

    解决方案

    阻塞队列方式实现

    生产者和消费者一定要使用一个阻塞队列

    take()----获取

    put()-----放入

    注意:这俩个方法的底层都是上了锁的,不用再格外的去加锁

    代码表示
    厨师(生产者)
    1. package com.FC;
    2. import java.util.concurrent.ArrayBlockingQueue;
    3. public class Cook extends Thread{
    4. ArrayBlockingQueue<String> queue;
    5. public Cook(ArrayBlockingQueue<String> queue){
    6. this.queue = queue;
    7. }
    8. public void run(){
    9. while(true){
    10. try {
    11. queue.put("面条");
    12. System.out.println("厨师放了一碗面条");
    13. } catch (InterruptedException e) {
    14. throw new RuntimeException(e);
    15. }
    16. }
    17. }
    18. }
    吃货(消费者)
    1. package com.FC;
    2. import java.util.concurrent.ArrayBlockingQueue;
    3. public class Foodie extends Thread{
    4. ArrayBlockingQueue<String> queue;
    5. public Foodie(ArrayBlockingQueue<String> queue){
    6. this.queue = queue;
    7. }
    8. public void run(){
    9. while(true){
    10. try {
    11. String food = queue.take();
    12. System.out.println("我吃了一碗面条");
    13. } catch (InterruptedException e) {
    14. throw new RuntimeException(e);
    15. }
    16. }
    17. }
    18. }
    运行代码
    1. package com.FC;
    2. import java.util.concurrent.ArrayBlockingQueue;
    3. public class ThreadDemo {
    4. public static void main(String[] args) {
    5. //创建阻塞队列的对象
    6. ArrayBlockingQueue<String > queue = new ArrayBlockingQueue<>(1);
    7. //创建线程
    8. Cook c = new Cook(queue);
    9. Foodie f = new Foodie(queue);
    10. //给线程设置名字
    11. c.setName("厨师");
    12. f.setName("吃货");
    13. //开启线程
    14. c.start();
    15. f.start();
    16. }
    17. }

    线程的状态

    线程池

    当有任务出现的时候,如果线程池里面为空,就创建一个新的线程,用于执行这个任务,执行完之后,把这个线程放到线程池里面,当下一个任务出现的时候,就不用创建新的线程了,直接从线程池里面拿现成的线程执行任务

    用工具类实现

    1.创建一个池子

    1. //没有上限的线程池
    2. public static ExecutorService newCachedThreadPool();
    3. //有上限的线程池
    4. public static ExecutorService newFixedThreadPool(int nThreads);
    ExecutorService pool1 = Executors.newCachedThreadPool();

    2.提交任务

    pool1.submit(new MyRunnable());

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

    pool1.shutdown();

    自定义实现

    自定义线程池中有核心线程和临时线程(都有一定的数量),当有很多任务要进行的时候,核心线程先进行一部分,然后一些任务排在队列里面(有一定的长度),当队列排满了之后,才会创建临时线程。

    如果核心线程加上临时线程再加上队伍的长度都小于要执行的任务,那么默认会采用抛弃策略

    1. ThreadPoolExecutor pool = new ThreadPoolExecutor(
    2. 3,//核心线程数,不能小于0
    3. 6,//最大线程数,不能小于0,且要大于等于核心线程数
    4. 60,//空闲线程最大存活时间
    5. TimeUnit.SECONDS,//时间单位
    6. new ArrayBlockingQueue<>(3),//任务队列
    7. Executors.defaultThreadFactory(),//创建线程工厂
    8. new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
    9. );

    其他

    获取电脑的可用的处理器数目

    int count = Runtime.getRuntime().availableProcessors();

    线程池大小的定夺

  • 相关阅读:
    Java中的多线程如何理解——精简
    InfoGAN原理PyTorch实现Debug记录
    SpringBoot集成Shiro安全框架
    【Flask基础】六,拦截器/请求钩子(全局+模块+资源选择性放行)
    static_assert
    代码随想录算法训练营第38天—动态规划06 | ● 完全背包 ● *518. 零钱兑换 II ● 377. 组合总和 Ⅳ
    PCL - 3D点云配准(registration)介绍
    DGIOT实战教程-监控摄像头接入(v4.6.0)
    简单理解注意力机制与实现
    3d-DNA组装基因组
  • 原文地址:https://blog.csdn.net/qq_74455082/article/details/133137852