Java中多线程创建的四种方法,继承Thread类创建多线程、实现Runnable接口、通过Callable和Future创建以及线程池的使用


前言

为什么要使用线程?

在程序中完成某⼀个功能的时候,我们会将他描述成任务,这个任务需要在线程中完成.


一、串⾏与并发

如果在程序中,有多个任务需要被处理,此时的处理方式可以有串行和并发:

  • 串行(同步):所有的任务,按照⼀定的顺序,依次执行。如果前⾯的任务没有执⾏结束,后⾯的任务等待。
  • 并发(异步):将多个任务同时执行,在⼀个时间段内,同时处理多个任务。

二、进程和线程

  • 进程:是对⼀个程序在运行过程中,占用的各种资源的描述。
  • 线程:是进程中的⼀个最小的执行单元。其中操作系统中,最小的任务执⾏单元并不是线程,而是句柄。只不过句柄过小,操作起来⾮常的麻烦,因此线程就是我们可控的最小的任务执⾏单元。

1.进程和线程的异同

  • 相同点: 进程和线程都是为了处理多个任务并发⽽存在的。
  • 不同点: 进程之间是资源不共享的,⼀个进程中不能访问另外⼀个进程中的数据。⽽线程之间是资源共享的,多个线程可以共享同⼀个数据。也正因为线程之间是资源共享的,所以会出现临界资源的问题。

2.进程和线程的关系

⼀个进程,在开辟的时候,会⾃动的创建⼀个线程,来处理这个进程中的任务。 这个线程被称为是主线程。在程序运⾏的过程中, 还可以开辟其他线程, 这些被开辟出来的其他线程, 都是⼦线程。
也就是说,⼀个进程中,是可以包含多个线程。⼀个进程中的某⼀个线程崩溃了,只要还有其他线程存在, 就不会影响整个进程的执行。 但是如果⼀个进程中,所有的线程都执行结束了,那么这个进程也就终止了。

3.进程与线程总结

  • 程序:⼀个可执行的⽂件
  • 进程:⼀个正在运行的程序.也可以理解成在内存中开辟了⼀块儿空间
  • 线程:负责程序的运⾏,可以看做⼀条执⾏的通道或执⾏单元,所以我们通 常将进程的⼯作理解成线程的⼯作
  • 进程中可不可以没有线程? 必须有线程,⾄少有⼀个.
  • 当有⼀个线程的时候我们称为单线程(唯⼀的线程就是主线程). 当有⼀个以上的线程同时存在的时候我们称为多线程.
  • 多线程的作⽤:为了实现同⼀时间⼲多件事情(并发执⾏).

三、线程的创建

1.线程创建的四种方法

创建线程主要有四种方法:

  • 继承Thread类创建线程对象
  • 使用Runnable接口
  • 通过 Callable 和 Future 创建线程
  • 通过线程池创建线程

2.继承Thread类创建线程对象

继承⾃Thread类,做⼀个Thread的⼦类。在⼦类中, 重写⽗类中的run⽅法,在这个重写的⽅法中,指定这个线程需要处理的任务。

代码示例:

/**
 * @author: Mercury
 * Date: 2022/3/27
 * Time: 13:15
 * Description:通过继承Thread来创建线程
 * Version:1.0
 */
public class Test3 {
    
    
    public static void main(String[] args) {
    
    
        Task2 task2 = new Task2("通过继承Thread创建的线程1");
        task2.start();
    }
}
class Task2 extends Thread{
    
    
    private String threadName;

    public Task2() {
    
    
    }

    public Task2(String threadName) {
    
    
        this.threadName = threadName;
    }
    @Override
    public void run() {
    
    
        for (int i = 0; i < 10; i++) {
    
    
            System.out.println(threadName+": "+i);
        }
    }
}

1) Thread类常用方法

方法名 描述
currentThread() 返回对当前正在执行的线程对象的引用
getId() 返回此线程的标识符
getName() 返回此线程的名称
getPriority() 返回此线程的优先级
getState() 返回此线程的状态
setName(String name) 将此线程的名称更改为参数name
sleep(long millis) 使当前的线程以指定的毫秒数暂停
start() 使此线程开始执行
toString() 返回此线程的名称、优先级和线程组
yield() 对调度程序的一个暗示,即当前线程愿意产生当前使用的处理器

3.使用Runnable接口

在Thread类的构造⽅法中,有⼀个重载的构造⽅法,参数是Runnable 接⼝。因此,可以通过Runnable接⼝的实现类对象进⾏Thread对象的实例化。

代码示例:

/**
 * @author: Mercury
 * Date: 2022/3/27
 * Time: 13:05
 * Description:通过实现Runnable接口来创建线程
 * Version:1.0
 */
public class Test2 {
    
    
    public static void main(String[] args) {
    
    
        Task task = new Task();
        Thread thread = new Thread(task,"线程1");
        thread. start();
    }
}
class Task implements Runnable{
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 10; i++) {
    
    
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

5.继承Thread和使用Runnable接口的优缺点对比

  • 继承的⽅式:优点在于可读性⽐较强,缺点在于不够灵活。如果要定制⼀个线程,就必须要继承⾃Thread类,可能会影响原有的继承体系。
  • 接⼝的⽅式:优点在于灵活,并且不会影响⼀个类的继承体系。缺点在于可读性较差。

6.通过 Callable 和 Future 创建线程

创建Callable接口的实现类,并实现call()方法,该方法可以作为线程执行体,并且有返回值。接下来创建Callable实现类的实例,并使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法返回值。使用FutureTask对象作为Thread的target创建并启动新线程。然后调用FutureTask对象的get()来获得子线程执行结束后的返回值。如果不需要接受返回值,可以不用这部分操作。

代码示例:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * @author: Mercury
 * Date: 2022/3/27
 * Time: 13:29
 * Description:通过 Callable 和 Future 创建线程
 * Version:1.0
 */
public class Test4 {
    
    
    public static void main(String[] args) {
    
    
        CallableTest callableTest = new CallableTest();
        //使用FutureTask类包装Callable对象
        FutureTask<Integer> futureTask = new FutureTask<Integer>(callableTest);
        //将FutureTask的对象作为参数传递到Thread类中,创建Thread对象,并调用start()
        new Thread(futureTask).start();
        //如果需要获取返回值的话
        try {
    
    
            Integer i = futureTask.get();
            System.out.println("i="+i);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } catch (ExecutionException e) {
    
    
            e.printStackTrace();
        }
    }
}
//创建Callable接口的实现类
class CallableTest implements Callable<Integer>{
    
    

    //实现call()方法
    @Override
    public Integer call() throws Exception {
    
    
        int i = 0;
        for (; i < 100; i++) {
    
    
            System.out.println(Thread.currentThread().getName()+": "+i);
        }
        return i;
    }
}

6.通过线程池创建线程

线程池:其实就是⼀个容器,⾥⾯存储了若⼲个线程。

使⽤线程池,最主要是解决线程复⽤的问题。之前使⽤线程的时候,当我 们需要使⽤⼀个线程时,实例化了⼀个新的线程。当这个线程使⽤结束后,对这个线程进⾏销毁。对于需求实现来说是没有问题的,但是如果频繁的进⾏线程的开辟和销毁,其实对于CPU来说,是⼀种负荷,所以要尽量的优化这⼀点。

可以使⽤复⽤机制解决这个问题。当我们需要使⽤到⼀个线程的时候,不是直接实例化,⽽是先去线程池中查找是否有闲置的线程可以使⽤。如果有,直接拿来使⽤;如果没有,再实例化⼀个新的线程。并且当这个线程使⽤结束后,并不是⻢上销毁,⽽是将其放⼊到线程池中,以便下次继续使⽤。

线程池中的所有线程可以分成俩个部分:核心线程和临时线程

核心线程:核⼼线程常驻于线程池中,这些线程,只要线程池存在,他们不会被销毁。只有当线程池需要被销毁的时候,他们才会被销毁。

临时线程:就是临时⼯。当遇到了临时的⾼密度的线程需求时, 就会临时开辟⼀些线程,处理⼀些任务。这些临时的线程在处理完⾃⼰需要处理的任务后, 如果没有其他的任务要处理,就会闲置。当闲置的时间到达了指定的时间之后,这个临时线程就会被销毁。

代码示例:

package test;

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

/**
 * @author: Mercury
 * Date: 2022/3/27
 * Time: 12:48
 * Description:线程池
 * Version:1.0
 */
public class Test1 {
    
    
    public static void main(String[] args) {
    
    
        //创建线程池对象,参数10 表示线程池中有10个线程
        ExecutorService service = Executors.newFixedThreadPool(10);
        //创建Runnable线程对象
        Task3 task3 = new Task3();
        //从线程池中获取线程对象
        service.submit(task3);
        service.submit(task3);
        //关闭线程池
        service.shutdown();
    }
}
class Task3 implements Runnable{
    
    

    @Override
    public void run() {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            System.out.println(Thread.currentThread().getName()+": "+i);
        }
    }
}

1.线程池常用方法

方法 描述
execute(Runnable runnable) 将任务提交给线程池,由线程池分配线程来并发处理
shutdown() 向线程池发送一个停止信号,并不会停止线程池中的线程,而是在线程池中所有任务结束执行后,结束线程和线程池
shutdownNow() 立即停止线程池中的所有线程和线程池

2.线程池的工具类

线程池的开辟,除了可以使⽤构造⽅法进⾏实例化,还可以通过Executors⼯具类进⾏获取。实际应⽤中,⼤部分的场景下,可以不⽤前⾯的构造⽅法进⾏线程池的实例化,⽽是⽤Executors⼯具类中的⽅法进⾏获取。

方法 描述
Executors.newSingleThreadExecutor() 核心线程数:1,最大线程数:1
Executors.newFixedThreadPool(int size) 核心线程size,最大线程size
submit() 向线程池中添加任务
shutdown() 停⽌线程池

总结

以上就是与多线程的相关内容,简单介绍了进程和线程以及多线程的四种创建方式,祝各位小伙伴们学习愉快!

猜你喜欢

转载自blog.csdn.net/qq_45263520/article/details/123774458