深度解析---Java并发编程

Java并发编程

  • Java并发编程
    • 1.Java线程的创建方式
      • 1.1 继承 Thread 类
      • 1.2 实现 Runnable 接口
      • 1.3 通过 ExecutorService 和 Callable 实现有返回值的线程
    • 2. 线程池的工作原理
      • 2.1 线程复用
      • 2.2 线程池的核心组件和核心类

Java并发编程

1.Java线程的创建方式

常见的 Java 线程的 4 种创建方式分别为:继承 Thread 类、实现 Runnable 接口、通过ExecuteService 和 Callable 实现有返回值得线程、基于线程池。如下图所示:

深度解析---Java并发编程_第1张图片

1.1 继承 Thread 类

Thread 类实现了 Runnable 接口并定义了操作线程的一些方法,我们可以通过继承 Thread 类的方法创建一个线程。具体实现为创建一个类并继承 Thread 接口,然后实例化线程对象并调用 start 方法启动线程。

start 方法是一个 native 方法,通过在操作系统上启动一个新线程,并最终执行 run 方法来启动一个线程。run 方法内的代码是线程类的具体实现逻辑。代码如下:

//用过继承Thread类创建NewThread线程
    public class NewThread extends Thread{
     
        @Override
        public void run(){
     
            System.out.println("create a thread by extends Thread");
        }
    }
    //实例化一个NewThread线程对象
    NewThread newThread = new NewThread();
    //调用 start 方法启动 NewThread 线程
    newThread.start();

1.2 实现 Runnable 接口

基于 Java 编程语言的规范,如果子类已经继承(extends)了一个类,就无法继承 Thread 类,此时可以通过实现 Runnable 接口创建线程。具体的实现过程为:

通过实现 Runnable 接口 ===> 创建 ChildrenClassThread 线程,实例化名称为 childrenThread 的线程实列 ===> 创建 Thread 类的实列并传入 childrenThread 线程实例 ===> 调用线程的 start 方法启动线程。实现代码如下:

   //通过实现 Runnable 接口方式创建 ChildrenClassThread 线程
    public class ChildrenClassThread extends SuperClass implements Runnable {
     

        @Override
        public void run() {
     
            System.out.println("create a thread by implements Runnable");
        }
    }

    // 实例化一个 ChildrenClassThread
    ChildrenClassThread childrenClassThread = new ChildrenClassThread();

    //创建一个线程对象并将其传入已经实例化好的 childrenThread
    Thread thread = new Thread(childrenClassThread);

    // 调用 start 方法启动一个线程
    thread.start();

事实上,在传入一个实现了 Runnable 的线程实例 target 给 Thread后,Thread 的run 方法在执行时就会调用 target,run 方法并执行该线程具体的实现逻辑。

在 JDK 源码中, run 方法的实现代码如下:

    @Override
    public void run(){
     
        if(target != null){
     
            target.run();
        }
    }

1.3 通过 ExecutorService 和 Callable 实现有返回值的线程

有时,我们需要在主线程中开启多个线程并发执行一个任务,然后收集各个线程执行的结果并将最终结果汇总起来,这时就要用到 Callable 接口。

具体实现方法为:创建一个类并实现 Callable 接口,在 call 方法中实现具体的运算逻辑并返回计算结果。(具体调用流程为:创建一个线程池,一个用于接收返回结果的 Future List 及 Callable 线程实例,使用线程池提交并将线程执行之后的结果保存在 Future 中,在线程执行结束后遍历 Future List 中的 Future 对象,在该对象上调用 get 方法就可以获取 Callable 线程任务返回数据并汇总结果。)

代码流程:

/**
     * @Description: 通过实现 Callable 接口创建 MyCallable
     * 
     * @param null
     * @return: {@link null}
     * @Author: 甘秦胜
     * @Date: 2020/8/30 21:44
     */
    public class MyCallable implements Callable<String> {
     

        private String name;

        public MyCallable(String name) {
     
            this.name = name;
        }

        @Override
        public String call() throws Exception {
     
            return name;
        }
    }

    //创建一个固定大小为 5 的线程池
    ExecutorService pool= Executors.newFixedThreadPool(5);

    // 创建多个有返回值的任务列表 list
    List<Future> list=new ArrayList<Future>();

    for(int i = 0 ; i < 5 ; i++){
     
        
        //创建一个有返回值的线程实例
        MyCallable c = new MyCallable(i + " ");

        //提交线程,获取 Future 对象并将其保存到 Future List 中
        Future future = pool.submit(c);

        System.out.println("submit a callable Callable thread:" + i);
        list.add(future);
    }

    //关闭线程池,等待线程执行结束
    pool.shutdown();

    //遍历所有的线程的运行结果
    for(Future f : list){
     
        //从 Future 对象上获取任务的返回值,并将其结果输出到控制台
        System.out.println("get the result from cllable thread:"+ f.get().toString());
    }

1.4 基于线程池

线程是非常宝贵的计算资源,在每次需要时创建并在运行结束后销毁是非常浪费资源的。我们就可以使用缓存策略并使用线程池来创建线程,具体过程为创建一个线程池并用该线程池提交线程任务,实现代码如下:

public class ThreadTest {
     

    public static void main(String[] args) throws Exception {
     

        //创建大小为 10 的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(10);

        //提交多个线程任务并执行
        for (int i = 0; i < 10; i++) {
     
            threadPool.execute(new Runnable() {
     
                @Override
                public void run() {
     
                    System.out.println(Thread.currentThread().getName() + "is Running");
                }
            });
        }
    }
}

2. 线程池的工作原理

原理:

JVM 先根据用户的参数创建一定的数量的可运行的线程任务,并将其放入列队中,在线程创建后启动这些任务,如果线程数量超过了最大线程数量(用户设置的线程池的大小),则超出数量的线程排队等待,在有任务执行完毕后,线程池调度器会发现有可用的线程,进而再次从列队中取出任务并执行。

作用:

线程池的主要作用是线程复用、线程资源管理、控制操作系统的最大并发数,以保证系统高效(通过线程资源复用实现)且安全(通过控制最大线程并发数实现)地运行。

2.1 线程复用

在 Java 中,每个 Thread 类都一个 start 方法。在程序调用 start 方法启动线程时, Java 虚拟机会调用该类的 run 方法。

在 Thread 类的 run 方法中其实调用了 Runnable 对象中的 run 方法,因此可以继承 Thread 类,在 start 方法中不断循环调用传递进来的 Runnable 对象,程序就会不断执行 run 方法中的代码。可以将在循环方法中不断获取的 Runnable 对象放在 Queue 中,当前线程在获取一个 Runnable 对象之间可以是阻塞的,这样既能有效控制正在执行的线程个数,也能保证系统中正在等待执行的其他线程有序执行。

2.2 线程池的核心组件和核心类

Java 线程主要由以下 4 个核心组件组成:

a. 线程池管理器:用于创建并管理线程池

b. 工作线程:线程池中执行具体任务的线程。

c. 任务接口:用于定义工作线程的调度和执行策略,只有线程实现了该接口,
线程中的任务才能够被线程池调度。

d. 任务列队:存放待处理的任务,新的任务将会不断被加入队列中,
执行完成的任务将被从队列中移除。

Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executor、Executors、ExecutorService、ThreadPoolExcutor、Callable、Future、FutureTask 这几个核心类,具体的继承关系如下图所示:

深度解析---Java并发编程_第2张图片

其中 ThreadPoolExecutor 是构建线程的核心方法,该放定义如下:


 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime,
                              TimeUnit unit, BlockingQueue<Runnable> workQueue){
     
        this(corePoolSize,maximumPoolSize,keepAliveTime,unit,
                workQueue,Executors.defaultThreadFactory(),defaultHandler);
    }
    

ThreadPoolExecutor 构造函数的具体参数如下表:

序号 参数 说明
1 corePoolSize 线程池中核心线程的数量
2 maximumPoolSize 线程池中最大线程的数量
3 keepAliveTime 当前线程数量超过 corePoolSize 时,空闲线程的存活时间
4 unit KeepAliveTime 的时间单位
5 workQueue 任务队列,被提交但尚未被执行的任务存放地方
6 threadFactory 线程工厂,用于创建线程,可使用默认的线程工厂或自定义线程工厂
7 handler 由于任务过多或者其他原因导致线程池无法处理时的任务拒绝策略

你可能感兴趣的:(Java,java,多线程)