多线程笔记

1、什么是多线程

  有了多线程,我们就可以让程序同时做很多件事。

2、多线程的作用?

   提高效率

3、多线程的应用场景?

  软件中的耗时操作、所有的服务器。把一个大的查询接口,细分几个线程去查询。

并发和并行:

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

并行:在同一时刻,有多个指令在多个cpu上同时执行。

4、创建线程的常用方式:

/**
 * 多线程的第一种启动方式:
 * 1、定义一个类继承Thread
 * 2、重写run方法
 * 3、创建子类的对象,并启动线程
 */
MyThread t1 = new MyThread();
t1.start();
MyThread t2 = new MyThread();
t2.start();
public class MyThread extends Thread {
    @Override
    public void run() {
        for(int i =0;i<100;i++){
            System.out.println("hello");
        }
    }
}

2、实现Runnable接口创建线程
通过实现Runnable接口创建并启动线程一般步骤如下:

1】定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体

2】创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象

3】第三部依然是通过调用线程对象的start()方法来启动线程

代码实例:
public class MyThread2 implements Runnable {//实现Runnable接口
  public void run(){
  //重写run方法
  }
}
public class Main {
  public static void main(String[] args){
    //创建并启动线程
    MyThread2 myThread=new MyThread2();
    Thread thread=new Thread(myThread);
    thread().start();
    //或者    new Thread(new MyThread2()).start();
  }
}

  

2、同步代码块格式:synchronized (锁对象){

      操作共享数据的代码

1)、锁默认打开,有一个线程进去了,锁自动关闭

2)、里边的代码全部执行完毕,线程出来,锁自动打开

3)、代码块是指用“{}”括起来的一段代码、同步代码块指在代码块前加上 synchronized关键   字的代码块。

4)锁对象必须是唯一的

 

这里用的锁对象是当前类的字节码文件对象,唯一的 。项目中一般都是使用同步代码块。因为粒度较小,速度越快。

4、死锁:当线程a,拿到锁后,刚准备去拿线程b的锁,线程b就抢先一步拿到了b锁,相互等待对方释放锁就形成了死锁

常用线城池:

21、在Java中,创建线程池有两种常见的方式:一般不允许使用Executors去创建线程池。

  1.通过 Executors 工具类提供的静态方法创建线程池。

  2.通过 ThreadPoolExecutor 构造函数自定义线程池。

通过 Executors 工具类提供的静态方法创建线程池:

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

public class ThreadPoolExample {

    public static void main(String[] args) {
        
        // 创建固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(5);

        // 提交任务给线程池
        for (int i = 0; i < 10; i++) {
            executor.execute(new Task());
        }

        // 关闭线程池
        executor.shutdown();
    }

    static class Task implements Runnable {
        public void run() {
            System.out.println("Executing task on thread: " + Thread.currentThread().getName());
        }
    }
}

2.通过 ThreadPoolExecutor 构造函数自定义线程池:

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

public class ThreadPoolExample {

    public static void main(String[] args) {
        
        // 自定义线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            2, // 核心线程数
            5, // 最大线程数
            1, // 空闲线程存活时间
            TimeUnit.SECONDS, // 存活时间单位
            new LinkedBlockingQueue<Runnable>() // 任务队列
        );

        // 提交任务给线程池
        for (int i = 0; i < 10; i++) {
            executor.execute(new Task());
        }

        // 关闭线程池
        executor.shutdown();
    }

    static class Task implements Runnable {
        public void run() {
            System.out.println("Executing task on thread: " + Thread.currentThread().getName());
        }
    }
}

corePoolSize 核心线程数量:

默认情况只有当新任务到达时,才会创建和启动核心线程,可用 prestartCoreThread()启动一个核心线程 和 prestartAllCoreThreads() 启动所有核心线程的方法动态调整即使没任务执行,核心线程也会一直存活池内的线程数小于核心线程时,即使有空闲线程,线程池也会创建新的核心线程执行任务设置allowCoreThreadTimeout=true时,核心线程会超时关闭
maximumPoolSize最大线程数:

当所有核心线程都在执行任务,且任务队列已满时,线程池会创建新非核心线程来执行任务当池内线程数=最大线程数,且队列已满,再有新任务会触发RejectedExecutionHandler策略。
keepAliveTime TimeUnit 线程空闲时间:

如果线程数>核心线程数,线程的空闲时间达到keepAliveTime时,线程会被回收销毁,直到线程数量=核心线程
如果设置allowCoreThreadTimeout=true时,核心线程执行完任务也会销毁直到数量=0
workQueue 任务队列:

队列的说明请参考 :
ThreadFactory 创建线程的工厂

一般用来自定义线程名称,线程多的时候,可以分组区分识别
RejectedExecutionHandler 拒绝策略

最大线程数和队列都满的情况下,对新任务的处理方式,请参考下方的4种策略

 4种拒绝策略:

CallerRunsPolicy(调用者运行策略):使用当前调用的线程 (提交任务的线程) 来执行此任务

AbortPolicy(中止策略):拒绝并抛出异常 (默认)

DiscardPolicy(丢弃策略):丢弃此任务,不会抛异常

DiscardOldestPolicy(弃老策略):抛弃队列头部(最旧)的一个任务,并执行当前任务

线程池:Java线程池七个参数详解-腾讯云开发者社区-腾讯云

多线程应用场景:
场景1:当某个接口响应速度很慢的时候,可以使用多线程提升响应速度。前提是这个接口获取信息的逻辑互相独立,比如首页接口,需要获取列表A,列表B,列表C等,而列表ABC三者之间互相独立(也就是不需要获取到A,就能获取到B),互相之间没有关系。这种情况就可以使用多线程去优化,总耗时为获取3个列表当中耗时最长的那个。

public class Test {

    ExecutorService executorService = Executors.newFixedThreadPool(3);
    
    public List<Object> get(List<String> list, final int threadNum) throws Exception {
        // 保存各个线程执行后的结果集
        List<Future<List<String>>> futures = new ArrayList<>(3);
        Callable<List<String>> task1 = new Callable<List<String>>() {
            @Override
            public List<String> call() throws Exception {
                //获取列表A
                List<String> list1 = new ArrayList<>();
                return list1;
            }
        };
        Callable<List<String>> task2 = new Callable<List<String>>() {
            @Override
            public List<String> call() throws Exception {
                //获取列表B
                List<String> list2 = new ArrayList<>();
                return list2;
            }
        };

        Callable<List<String>> task3 = new Callable<List<String>>() {
            @Override
            public List<String> call() throws Exception {
                //获取列表C
                List<String> list2 = new ArrayList<>();
                return list2;
            }
        };
        //添加线程到队列
        futures.add(executorService.submit(task1));
        futures.add(executorService.submit(task2));
        futures.add(executorService.submit(task3));

        //主线程会阻塞在这里,直到3个子线程执行完成
        //结果集汇总
        List<Object> res = new ArrayList<>();
        for (int i = 0; i < futures.size(); i++) {
            res.add(futures.get(i).get());
        }
        executorService.shutdown();
        return res;
    }
}

30、ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。

31、线程池中线程复用的原理:

32、多线程的一个使用场景:

1、配置自定义的线程池,@EnableAsync:在类上添加@EnableAsync 注解,然后在需要异步的方法上使用@Async("线程池名称") 该方法就可以异步执行了。

@Configuration
@RefreshScope
@EnableAsync
public class ThreadPoolConfig {

    @Value("${threadPool.corePoolSize}")
    private int corePoolSize;
    @Value("${threadPool.maxPoolSize}")
    private int maxPoolSize;
    @Value("${threadPool.queueCapacity}")
    private int queueCapacity;
    @Value("${threadPool.keepAliveSeconds}")
    private int keepAliveSeconds;
    @Value("${threadPool.namePrefix}")
    private String namePrefix;

    @Bean(THREAD_POOL_EXECUTOR)
    public Executor threadPoolExecutor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        //线程池创建的核心线程数,线程池维护线程的最少数量,即使没有任务需要执行,也会一直存活
        //如果设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
        executor.setCorePoolSize(corePoolSize);

        //最大线程池数量,当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
        //当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
        executor.setMaxPoolSize(maxPoolSize);

        //缓存队列(阻塞队列)当核心线程数达到最大时,新任务会放在队列中排队等待执行
        executor.setQueueCapacity(queueCapacity);

        //当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
        //允许线程空闲时间60秒,当maxPoolSize的线程在空闲时间到达的时候销毁
        //如果allowCoreThreadTimeout=true,则会直到线程数量=0
        executor.setKeepAliveSeconds(keepAliveSeconds);

        //spring 提供的 ThreadPoolTaskExecutor 线程池,是有setThreadNamePrefix() 方法的。
        //jdk 提供的ThreadPoolExecutor 线程池是没有 setThreadNamePrefix() 方法的
        executor.setThreadNamePrefix(namePrefix);
        executor.setWaitForTasksToCompleteOnShutdown(true);
        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CallerRunsPolicy():交由调用方线程运行,比如 main 线程;如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行
        //AbortPolicy():该策略是线程池的默认策略,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。
        //DiscardPolicy():如果线程池队列满了,会直接丢掉这个任务并且不会有任何异常
        //DiscardOldestPolicy():丢弃队列中最老的任务,队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
        executor.initialize();
        return executor;
    }

}

2、再需要执行的方法上注解 @Async,括号里边是线程池的Bean name。 

mq 消费端结合线程池:

截图中可以看到mq的消费端使用的是自定义的 消费工厂,开启了多线程的监听(可以观察当队列有多个任务时消费端每次只消费一个消息,单线程处理消息容易引起消息处理缓慢,消息堆积,不能最大利用硬件资源。可以配置mq的容器工厂参数,增加并发处理数量即可实现多线程处理监听队列,实现多线程处理消息),当有多个线程来处理业务时,就需要开启异步,所以再 handle()方法上开启了异步处理。

指令重排(减少执行指令的次数,来提高执行效率):a b xy 四个变量默认为0,两个线程同时执行变量,正常情况 线程A执行 x=a b=1,线程B执行 y=b a=2,那么结果 x=0;y=0;;由于变量之间没有依赖那么虚拟机就会指令重排认为没有影响,这只是对单线程来说没影响,但对多线程来说不一样了,指令重排后,执行 结果发生变化。volatile可解决指令重排和线程之间可见

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值