Java多线程理解及实现

一、什么是线程?

 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。简单理解就好比我们坐高铁、飞机过安检一样,过安检的时候一个入口只有一个安检口,而多线程就是为安检开启了多个安检口。Java在语言层面对多线程提供了卓越的支持。

二、线程和进程有什么区别?

 线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。每个线程都拥有单独的栈内存用来存储本地数据。

三、 如何在Java中实现线程?

线程实现的三种方式:

  • 继承Thread类创建线程类

  步骤:

===> 创建一个继承于Thread类的子类 

===> 重写Thread类的run()方法

===> 创建Thread类的子类的对象

===> 通过此对象调用start()方法启动线程

复制代码
public class FirstThreadTest extends Thread{
    //重写run方法,run方法内就是线程要执行的业务逻辑代码
    public void run(){
        for(int i = 0;i<100;i++){
            System.out.println(getName()+" "+i);
        }
    }
    public static void main(String[] args){
        for(int i = 0;i< 100;i++){
            // Thread.currentThread()方法返回当前正在执行的线程对象,getName()方法返回线程名称,此处线程暂未开启,所以打印的是Main线程名
            System.out.println(Thread.currentThread().getName()+" :"+i);
            if(i==20){
                // 启动连个线程
                new FirstThreadTest().start();
                new FirstThreadTest().start();
            }
        }
    }
}
复制代码
  •  通过Runnable接口创建线程类 (常用)

  步骤:

===>  定义Runnable接口实现类,并重写run()方法【和Thread类中的run()方法是一样的,都是来自Runnable接口的】

===>  创建实现类的对象,将实现类的对象作为参数传递到Thread类的构造器中

===>  通过Thread类对象调用start()方法启动线程

复制代码
public class RunnableThreadTest implements Runnable {
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 20) {
                // 创建实现类对象
                RunnableThreadTest rtt = new RunnableThreadTest();
                // 以参数的形式传入Thread类的构造器中,并给当前线程重命名
                new Thread(rtt, "新线程1").start();
                new Thread(rtt, "新线程2").start();
            }
        }
    }
}
复制代码
  •  通过线程池实现多线程 (最常用且最实用)

  在Java中juc包中有一个Executors工具类,可以为我们创建一个线程池,其本质就是new了一个ThreadPoolExecutor对象。

  1. Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)
  2. Executors.newFixedThreadPool(int)(固定大小线程池)
  3. Executors.newSingleThreadExecutor()(单个后台线程)
  4. Executors.newScheduledThreadPool() (创建固定大小的线程,可以延迟或定时的执行任务)

 线程池体系结构简化图:

Tips:蓝色实线箭头是类的继承;绿色虚线箭头是接口的实现,绿色实线箭头是接口的继承 

  Executors 工具类通过提供一系列的工厂方法来创建线程池,返回的线程池(就是ThreadPoolExecutor对象)都实现了 ExecutorService 接口,ExecutorService接口继承了Executor接口,提供了更丰富的方法来管理线程池,ExecutorService对象可以通过execute(Runnable)或 submit(Callable)方法来开始执行新的方法。

  ExecutorService(其实就是线程池)的生命周期包括三种状态,运行,关闭,终止。创建后便进入运行状态。调用shutdown() 方法就进入关闭状态。此时ExecutorService不再接受新的任务,但是它还在执行已经提交的任务,等到所有的任务都执行完毕后,就到了终止状态。

示例代码:

复制代码
public class ScheduledThreadTest  {
    public static void main(String[] args) {
        System.out.println("当前线程: " + Thread.currentThread().getName() + "===========》start");
        // 创建支持计划任务的线程池
        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(2);
        // delay:延迟   延迟两秒执行任务
        threadPool.schedule(() -> System.out.println("定时任务"),2, TimeUnit.SECONDS);
        System.out.println("当前线程: " + Thread.currentThread().getName() + "===========》end");
        threadPool.shutdown();
    }
}
复制代码

输出结果:(通过实际输出,可以看到 ,两秒钟之后,才开始执行任务当中的打印 语句)

 =============================

周期性的定时任务示例代码:

复制代码
public class ScheduledThreadTest2 {
    public static void main(String[] args) {
        System.out.println("当前线程: " + Thread.currentThread().getName() + "===========》start");
        // 创建支持计划任务的线程池
        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(2);
        /**
         *       scheduleWithFixedDelay(Runnable command,long initialDelay, long delay,TimeUnit unit)
         *         第一个command参数是任务实例,
         *         第二个initialDelay参数是初始化延迟时间,
         *         第三个delay参数是延迟间隔时间,
         *         第四个unit参数是时间单元。
         */
        threadPool.scheduleWithFixedDelay(new Runnable() {
            int count = 0;
            @Override
            public void run() {
                System.out.println("count = " + count++);
            }
        }, 3, 2, TimeUnit.SECONDS);
        System.out.println("当前线程: " + Thread.currentThread().getName() + "===========》end");
//        threadPool.shutdown(); // 不能调用此方法,否则定时任务无法启动(延期3秒启动,但是线程池已经关闭了)
    }
}
复制代码

输出结果:

…..后面每间隔2s,输出一次。


 Callable接口

  现在我们知道了怎么创建线程:一种是通过创建Thread类,另一种是通过使用Runnable创建线程。但是,Runnable缺少的一项功能是,当线程终止时(即run()完成时),我们无法使线程返回结果。为了支持此功能,Java中提供了Callable,Callable接口使用泛型去定义它的返回类型。

  • 为了实现Runnable,需要实现不返回任何内容的run()方法,而对于Callable,需要实现在完成时返回结果的call()方法。请注意,不能使用Callable创建线程,只能使用Runnable创建线程。
  • 另一个区别是call()方法允许抛出异常,而run()则不能。
  • 为实现Callable而必须重写call方法。

Future接口

  当call()方法完成时,结果必须存储在主线程已知的对象中,以便主线程可以知道该线程返回的结果。为此,可以使用Future对象。将Future视为保存结果的对象–它可能暂时不保存结果,但将来会保存(一旦Callable返回)。因此,Future基本上是主线程可以跟踪进度以及其他线程的结果的一种方式。

  Executors工具类提供了一些有用的方法在线程池中执行Callable内的任务。由于Callable任务是并行的(并行就是整体看上去是并行的,其实在某个时间点只有一个线程在执行),我们必须等待它返回的结果。 java.util.concurrent.Future对象为我们解决了这个问题。在线程池提交Callable任务后返回了一个Future对象,使用它可以知道Callable任务的状态和得到Callable返回的执行结果。Future提供了get()方法让我们可以等待Callable结束并获取它的执行结果。

  要实现此接口,必须重写5种方法:

 

   要创建线程,需要Runnable。为了获得结果,需要future。Java库具有具体的FutureTask类型,该类型实现Runnable和Future,并将这两种功能组合在一起。可以通过为其构造函数提供Callable来创建FutureTask。然后,将FutureTask对象提供给Thread的构造函数以创建Thread对象。因此,间接地使用Callable创建线程。

 使用Callable和Future的完整示例:

复制代码
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;

public class TestCallable implements Callable<Object> {

    private int taskNum;

    public TestCallable(int taskNum) {
        this.taskNum = taskNum;
    }

    //1,2主要区别是创建线程的方式
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test1();
        test2();

    }

    /**
     * 使用Executors.newFixedThreadPool创建线程池
     * @throws InterruptedException
     * @throws ExecutionException
     */
    private static void test1() throws InterruptedException, ExecutionException {
        System.out.println("----程序开始运行----");
        Date date1 = new Date();
        int taskSize = 5;
        ExecutorService pool = Executors.newFixedThreadPool(taskSize);
        List<Future> list = new ArrayList<Future>();
        for (int i = 0; i < taskSize; i++) {
            Callable c = new TestCallable(i);
            // 执行任务并获取Future对象
            Future f = pool.submit(c);
            list.add(f);
        }
        // 关闭线程池
        pool.shutdown();
        // 获取所有并发任务的运行结果
        for (Future f : list) {
            // 从Future对象上获取任务的返回值,并输出到控制台
            System.out.println(">>>" + f.get().toString()); //OPTION + return 抛异常
        }
        Date date2 = new Date();
        System.out.println("----程序结束运行----,程序运行时间【" + (date2.getTime() - date1.getTime()) + "毫秒】");
    }

    /**
     * 线程直接使用new Thread来创建
     * @throws ExecutionException
     * @throws InterruptedException
     */
    private static void test2() throws ExecutionException, InterruptedException {
        System.out.println("----程序开始运行----");
        Date date1 = new Date();
        int taskSize = 5;
        FutureTask[] randomNumberTasks = new FutureTask[5];
        List<Future> list = new ArrayList<Future>();
        for (int i = 0; i < randomNumberTasks.length; i++) {
            Callable c = new TestCallable(i);
            // 执行任务并获取Future对象
            randomNumberTasks[i] = new FutureTask(c);
            Thread t = new Thread(randomNumberTasks[i]);
            t.start();
        }

        // 获取所有并发任务的运行结果
        for (Future f : randomNumberTasks) {
            // 从Future对象上获取任务的返回值,并输
            System.out.println(">>>" + f.get().toString()); //OPTION + return 抛异常
        }
        Date date2 = new Date();
        System.out.println("----程序结束运行----,程序运行时间【" + (date2.getTime() - date1.getTime()) + "毫秒】");
    }

    /**
     * call方法的实现,主要用于执行线程的具体实现,并返回结果
     * @return
     * @throws Exception
     */
    @Override
    public Object call() throws Exception {
        System.out.println(">>>" + taskNum + "任务启动");
        Date dateTmp1 = new Date();
        Thread.sleep(1000);
        Date dateTmp2 = new Date();
        long time = dateTmp2.getTime() - dateTmp1.getTime();
        System.out.println(">>>" + taskNum + "任务终止");
        return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";
    }
}
复制代码

运行结果:

 

posted @   danielzzz  阅读(173)  评论(0编辑  收藏  举报
(评论功能已被禁用)
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示