多线程基础

一、程序、进程、线程

(一)程序、进程、线程概念

        程序:一系列有组织的文件,封装在操作系统的各种API,实现不同的效果。

        进程:程序在系统中的一个执行过程。进程是现代操作系统中资源分配(CPU、内存等关键系统资源)的最小单位,不同进程之间是相互独立的。(打开Edge浏览器-->就是打开一个进程)(win下的任务管理器- 就能看到进程信息)

        线程:线程就是进程中的一个子任务。

(二)进程和线程的区别

  1. 进程是OS资源分配的基本单位,线程是OS系统调度的基本单位。
  2. 创建和销毁进程的开销要远比创建和销毁线程的开销大得多(创建和销毁一个进程的时间要比创建和销毁一个线程的时间大得多),线程更加轻量化。
  3. 调度一个线程也远比调度一个进程快得多。
  4. 进程包含线程,每个进程至少包含一个线程(主线程)。
  5. 进程之间彼此相对独立,不同的进程之间不会共享内存空间;同一个进程的线程共享该进程的内存空间。

(三)为什么需要线程?

        首先, "并发编程" 成为 "刚需"。

                单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU. 而并发编程能更充分利用多核 CPU 资源.

                有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编程.

        其次, 虽然多进程也能实现 并发编程, 但是线程比进程更轻量.

                创建线程比创建进程更快.

                销毁线程比销毁进程更快.

                调度线程比调度进程更快.

多线程最大的应用场景:把一个大任务拆分成多个子任务(交给子线程),多个子线程并发执行,提高系统的处理效率。

(四)Java线程 和 操作中系统线程的关系

        线程实际上是操作系统中的概念,操作系统内核实现了线程这样的机制, 并且对用户层提供了一些 API 供用户使用。

        Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装.

        Java 中JDK提供的线程库实际上就是利用操作系统提供的线程库进行的二次封装。

二、创建线程的四种方式

a.继承Thread类,覆写run方法(线程的核心工作任务方法)        

a方式下线程的创建与启动

       1. 一个子类继承Thread类

        2.覆写run方法

        3.产生当前这个子类对象,而后调用start方法启动线程

public class ThreadMethod extends Thread{
    @Override
    public void run() {
        System.out.println("这是子线程的输出结果");
    }
}
public class Main {
    public static void main(String[] args) {
        ThreadMethod mt = new ThreadMethod();
        mt.start();
        System.out.println("这是主线程的输出语句~~");
    }
}

     

b.实现Runnable接口,覆写run方法

b方式下程的创建与启动

       1.实现Runnable接口,

       2.覆写run方法

       3.创建线程的任务对象

       4.创建Thread类的对象,将任务对象传入线程对象

       5.调用start()方法,启动线程

/**
 * Created with IntelliJ IDEA.
 * Description: 实现 runnable 接口,覆写run方法
 * 这个实现了runnable接口的子类,并不是直接的线程对象,只是一个线程的核心工作任务
 * 线程的任务和线程实体之间的关系
 */

public class RunnableMethod implements Runnable{
    @Override
    public void run() {
        System.out.println("这是Runnable方式实现的子线程任务");
    }
}
public class Main {
    public static void main(String[] args) {
        Runnable runnableMethod = new RunnableMethod();
        Thread thread =new Thread(runnableMethod);
        thread.start();
        System.out.println("这是主线程的输出语句~~");
    }
}

a,b两种方法最终启动线程都是通过Thread类的start方法启动线程的

调用start方法启动线程,是由JVM产生操作系统的线程并启动,到底什么时候真正启动,对于我们来说是不可见的,也无法控制。

 推荐使用方式b:使用Runnable接口更加灵活,子类还能实现别的接口,继承别的类,方式a 只能继承Thread 单继承局限

 关于a,b两种方式的不同写法:

        1. 使用匿名内部类 继承 Thread 类

  1.         Thread  thread  =  new  Thread(){ 覆写run方法 }
public class OtherMethod {
    public static void main(String[] args) {
        Thread t1 = new Thread(){
            @Override
            public void run() {
                System.out.println("匿名内部类继承Thread类");
                System.out.println(Thread.currentThread().getName());
            }
        };
        t1.start();
        System.out.println("这是主线程" + Thread.currentThread().getName());
    }
}

       2.使用匿名内部类实现Runnable 接口

        Thread  thread  =  new  Thread(new Runnable(){ 覆写run方法 });

public class OtherMethod {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名内部类实现Runnable接口");
                System.out.println(Thread.currentThread().getName());
            }
        });
        thread.start();
        System.out.println("这是主线程" + Thread.currentThread().getName());
    }
}

     3. 使用lambda表达式

public class OtherMethod {
    public static void main(String[] args) {

        Thread thread2 = new Thread(()-> System.out.println("Lambda表达式实现Runnable接口"));
        thread2.start();
        System.out.println("这是主线程" + Thread.currentThread().getName());
    }
}

c.实现Callable接口,覆写call方法

Callable接口是带返回值的工作方法接口,线程核心方法是call方法

      1.创建Callable任务对象

      2. Callable接口的返回值使用FutureTask子类来接收

      3. Callable接口的对象最终也是通过Thread类的start方法启动线程

      4. 调用FutureTask的get方法获取call方法的返回值

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // Callable接口是带返回值的工作方法接口,线程核心方法是call方法
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 0; i <= 1000; i++) {
                    sum += i;
                }
                return sum;
            }
        };
//1.  通过Thread类创建线程 
    // Callable接口的返回值使用FutureTask子类来接收
        FutureTask<Integer> task = new FutureTask<>(callable);
        // Callable接口的对象最终也是通过Thread类的start方法启动线程
        // 向Thread类中传入FutureTask对象(包含call方法)
        Thread thread = new Thread(task);
        thread.start();
        // 调用FutureTask的get方法获取call方法的返回值
        // 代用get方法的线程会一直阻塞,直到call方法执行完毕,有返回值才恢复执行
        Integer result = task.get();
        System.out.println("子线程执行结束,result = " + result);

//2.通过线程池创建线程
        ExecutorService pool = Executors.newFixedThreadPool(3);
        Future<Integer> callResult = pool.submit(callable);
        // get方法会阻塞当前线程,直到call方法执行完毕,才恢复执行
        int result2 = callResult.get();
        System.out.println("子线程执行结束,result2 = " + result2);
    }
}

d.使用线程池创建线程

1.什么是线程池 AND 为什么要创建线程池?

        线程池:系统内部创建好了若干个线程,这些线程都是runnable(状态),只需要从系统中取出任务,就可以立即开始执行任务。

        虽然创建销毁线程比创建销毁进程更轻量, 但是在频繁创建销毁线程的时候还是会比较低效。线程池最大的好处就是减少每次启动、销毁线程的损耗(提高时间和空间利用率)。

 

2.JDK内置线程池 

Executors: 线程池的工具类,内置了四大创建好的线程池

ExecutorService接口的四大核心方法:

 1. execute(不推荐):线程接收一个runnable对象并执行任务

提交一个任务(run方法或者call方法)到线程池,线程池就会派遣空闲的线程执行任务

2. submit(Runnable task)

3. submit (Callable task)

终止线程池中的所有线程,销毁线程池

4. shutdownNow()  立即尝试终止所有线程池中的线程(无论线程是否空闲)

5. shutdown()  停止所有处在空闲状态的线程,其他正在执行任务的线程,等待任务执行结束再停止。

public class ThreadPoolTest {
    public static void main(String[] args) {
        //固定大小线程池
        ExecutorService pool = Executors.newFixedThreadPool(10);
        //数量动态变化的线程池
        ExecutorService pool1 = Executors.newCachedThreadPool();
        // 只包含一个线程的单线程池
         ExecutorService pool2 = Executors.newSingleThreadExecutor();
         // 定期线程池,可以设置任务的延时启动时间(Timer类的线程池实现)
        // 定时器线程池
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);

        pool.submit(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName() + "hello" + i);
                }
            }
        });

        scheduledThreadPool.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+ "3s后执行此任务");
            }
        },3, TimeUnit.SECONDS);
    }

}

3.创建线程池

ThreadPoolExecutor-- 描述线程池的核心类(最常用的子类),这个类的构造方法就是创建一个线程池的所有核心参数。

(1)ThreadPoolExecutor 子类的核心构造方法参数:

corePoolSize: 正式线程的数量. (正式员工, 一旦录用, 永不辞退)

maximumPoolSize: 正式线程 + 临时线程的数目. (临时工: 一段时间不干活, 就被辞退).

 keepAliveTime: 临时线程允许的空闲时间.

unit: keepAliveTime 临时线程允许的空闲时间的时间单位, 是秒, 分钟, 还是其他值.

workQueue: 传递任务的阻塞队列

threadFactory: 创建线程的工厂, 参与具体的创建线程工作.

RejectedExecutionHandler: 拒绝策略, 如果任务量超出公司的负荷了接下来怎么处理.         AbortPolicy(): 超过负荷, 直接抛出异常.

        CallerRunsPolicy(): 调用者负责处理

        DiscardOldestPolicy(): 丢弃队列中最老的任务.

        DiscardPolicy(): 丢弃新来的任务.

 

(2)四大内置线程池的核心参数配置

固定大小线程池:

缓存池(动态变化的线程池):

单线程池 :

定时线程池:

(3)单线程池和创建一个线程的区别?(单线程池是否有意义?)

        单线程池的意义在于省去创建线程和销毁线程的时间,来一个任务执行一个任务,还未执行的任务在工作队列排队执行。

        如果此时有10个任务,如果只创建线程,需要创建10次,一个线程只执行一个任务,任务执行完毕就被销毁。而如果创建的是单线程池,虽然同一时间也只能执行一个任务,但当这个任务执行结束后,继续在工作队列调度一个新的任务继续执行。

 (4)线程池的工作流程

4.线程池中接口和类之间的关系(梳理) 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值