Java高级(10) 多线程

Java高级(十)-- 多线程

基本概念 – 程序、进程、线程

程序(Program)是为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象

进程(Process)是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,它有自身的产生、存在和消亡的过程,这个过程就是进程的生命周期

  • 程序是静态的,进程是动态的
  • 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

线程(Thread),进程可进一步细化为线程,是程序内部的一条执行路径

  • 若一个进程同一时间并行执行多个线程,就是支持多线程的
  • 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(PC),且线程的切换开销较小
  • 一个进程中的多个线程共享相同的内存单元 / 内存空间地址(共享同一个进程中的方法区、堆),它们从统一堆中分配对象,可以访问相同的变量和对象,这就使得线程间的通信更简便、高效,但多个线程共享系统资源可能带来安全隐患

并行与并发

  • 并行:多个CPU同时执行多个任务
  • 并发:一个CPU(采用时间片)同时执行多个任务

多线程优点

​ 以单核CPU为例,讲述使用多线程并发执行多个任务的优点

  • 提高应用程序的响应速度,对图形化界面更有意义,可增强用户体验
  • 提高计算机系统CPU的利用率
  • 改善程序结构,将长冗复杂的进程分为多个线程,独立运行,利于理解和修改

何时需要多线程

  • 程序需要同时执行两个或多个任务
  • 程序需要实现一些需要等待的任务,如:用户输入、文件读写操作、网络操作、搜索等
  • 需要一些后台运行的程序

多线程的创建与使用

Java中的 JVM 允许程序运行多个线程,它通过 java.lang.Thread 类来体现,此类的特性如下:

  • 每个线程都是通过某个特定的 Thread 对象的 run() 方法来完成操作的,所以常将 run() 方法称为线程体
  • 通过该 Thread 对象的 start() 方法来启动这个线程,而非直接调用 run() 方法

多线程的创建

方式一:继承 Thread 类

方式一:继承 Thread

  1. 创建一个继承于 Thread 类的子类

  2. 重写 Thread 类的 run() 方法,将此线程的操作声明在 run() 方法中

  3. 创建此继承 Thread 类的子类对象

  4. 通过此对象调用 start() 方法

    ① 启动当前线程

    ② 调用当前线程的 run() 方法

public class ThreadTest {
   
    public static void main(String[] args) {
   
        // 3. 创建此继承 Thread 类的子类对象
        MyThread t = new MyThread();
        // 4. 通过此对象调用 start() 方法
        t.start();

        // 如下操作在main线程中运行
        for (int i = 0; i < 50; i++) {
   
            System.out.println("Thread -- main" + i);
        }

        // 直接调用 run() 方法,依旧是主线程中的线程,并未开启另一线程执行
        // new MyThread().run();

        // 不能使用同一线程对象 start() 两次 java.lang.IllegalThreadStateException
        t.start();
    }
}

// 1. 创建一个继承于 Thread 类的子类
class MyThread extends Thread{
   

    // 2. 重写 Thread 类的 run() 方法,将此线程的操作声明在 run() 方法中
    @Override
    public void run() {
   
        for (int i = 0; i < 100; i++) {
   
            if (i % 2 == 0)
                System.out.println("Thread -- MyThread" + i);
        }
    }
}
方式二:实现 Runnable 接口

方式二:实现 Runnable 接口

  1. 创建一个实现了 Runnable 接口的类
  2. 实现类区实现 Runnable 中的抽象方法:run() 方法
  3. 创建实现类对象
  4. 将此对象作为参数传递到 Thread 类的构造器中,创建 Thread 类的对象
  5. 通过 Thread 类的对象调用 start()
/*
	为何能通过 Thread 类调用 Runnable 中重写的 run() 方法
	因为 Thread类中有一Runnable类型变量target,而Thread的run()方法中,若target非空,则通过target调用run()方法(Thread 也实现了Runnable接口)
*/

/* What will be run. */
private Runnable target;

// Thread 中的构造器有通过 Runnable
public Thread(Runnable target) {
    
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

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

    public static void main(String[] args) {
   
        // 3. 创建实现类对象
        MyRunnable runnable = new MyRunnable();
        // 4. 将此对象作为参数传递到 Thread 类的构造器中,创建 Thread 类的对象
        Thread t = new Thread(runnable);
        // 5. 通过 Thread 类的对象调用 start()
        t.start();
    }
}


// 1. 创建一个实现了 Runnable 接口的类
class MyRunnable implements Runnable{
   
    // 2. 实现类区实现 Runnable 中的抽象方法:run() 方法
    @Override
    public void run() {
   
        for (int i = 0; i < 100; i++) {
   
            // 此时不能直接调用Thread类中的方法,因为此类只是实现了Runnable接口,并未继承Thread类
            // 此时要通过currentThread()方法获取当前线程对象才能调用相应方法
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}

比较创建线程的两种方式

开发中优先选择使用实现 Runnable 接口的方式,其原因如下:

  1. 实现的方式没有类的单继承的局限性
  2. 实现方式更适合来处理多个线程有共享数据的情况

相同点:二者都需要重写 run() 方法,将线程要执行的逻辑声明在 run() 方法中

联系:Thread 类实现了 Runnable 接口

JDK5.0新增线程创建方式

方式三:实现 Callable 接口

方式三:实现 Callable 接口

  1. 创建一个实现 Callable 的实现类
  2. 实现 call() 方法
  3. 创建 Callable 接口的实现类对象
  4. 将此 Callable 接口的实现类对象作为参数传递到 FutureTask 的构造器中,创建 FutureTask 的对象
  5. 将 FutureTask 的对象作为参数传递给 Thread 类的构造器中,创建 Thread 类对象,调用 start()
  6. 可以通过 FutureTask 类的对象,调用 get() 方法,获取 Callable 的 call() 方法中的返回值

其中, FutureTask 是 Future 接口的实现类,而 Future 接口大概情况如下:

  • 可以对具体 Runnable 、Callable 任务的执行结果进行取消、查询是否完成、获取结果等
  • FutureTask 是 Future 接口的唯一的实现类
  • FutureTask 同时实现了 Runnable, Future 接口。它既可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值

与使用Runnable相比, Callable功能更强大

  1. 相比 run() 方法,call() 方法可以有返回值
  2. call() 方法可以抛出异常
  3. call() 方法支持泛型的返回值
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * Created by 911 on 2021/3/18.
 */
public class CallableTest {
   
    public static void main(String[] args) {
   
        // 3. 创建 Callable 接口的实现类对象
        NewThread callable = new NewThread();
        // 4. 将此 Callable 接口的实现类对象作为参数传递到 FutureTask 的构造器中,
        // 创建 FutureTask 的对象
        FutureTask futureTask = new FutureTask(callable);
        // 5. 将 FutureTask 的对象作为参数传递给 Thread 类的构造器中,创建 Thread 类对象,
        // 调用 start()
        Thread t = new Thread(futureTask);
        t.start();
        try {
   
            // 6. 可以通过 FutureTask 类的对象,调用 get() 方法,
            // 获取 Callable 的 call() 方法中的返回值
            System.out.println("偶数总和:" + futureTask.get());
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        } catch (ExecutionException e) {
   
            e.printStackTrace();
        }
    }
}

// 1. 创建一个实现 Callable 的实现类
class NewThread implements Callable{
   
    // 2. 实现 call() 方法
    // call() 方法返回值可以为 null
    @Override
    public Object call() throws Exception {
   
        int evenSum = 0;
        for (int i = 1; i <= 100; i++) {
   
            if (i % 2 == 0){
   
                System.out.println(i);
                evenSum += i;
            }
        }
        return evenSum;
    }
}
方式四:使用线程池

方式四:使用线程池

  1. 提供线程池
  2. 执行指定的线程操作,需要提供实现 RunnableCallable 接口的实现类对象
  3. 关闭线程池

线程池的意义:

​ 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。因此,提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,这样便可以避免频繁创建销毁、实现重复利用。

线程池的好处:

  • 提高响应速度(减少了创建新线程的时间)

  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)

  • 便于线程管理,如下提供一些关键参数:

    • corePoolSize:核心池的大小

    • maximumPoolSize:最大线程数

    • keepAliveTime:线程没有任务时最多保持多长时间后会终止

线程池相关API

从 JDK 5.0 起,提供了线程池相关API:ExecutorServiceExecutors

  • ExecutorService:真正的线程池接口,其常见子类 ThreadPoolExecutor

    • void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行 Runnable
    • Future submit(Callable task):执行任务,有返回值,一般用于执行 Callable
    • void shutdown() :关闭连接池
  • Executors:工具类、线程池的工厂类,用于创建返回不同类型的线程池

    • Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
    • Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
    • Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
    • Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行
public class ThreadPoolTest {
   
    public static void main(String[] args) {
   
        // 1. 提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        // Executors.newFixedThreadPool():此方法返回的是ThreadPoolExecutor对象
        // 可以强转为ThreadPoolExecutor对象,对线程的各属性进行操作
        ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) service;
        // 设置核心池大小
        threadPoolExecutor.setCorePoolSize(10);
        // 设置最大线程数
        threadPoolExecutor.setMaximumPoolSize(10);
        // 2. 执行指定的线程操作,需要提供实现 Runnable 或 Callable 接口的实现类对象
        service.execute(new RunnableClass());
        service.submit(new CallableClass());
        // 3. 关闭线程池
        service.shutdown();
    }
}

class RunnableClass implements Runnable{
   
    @Override
    public void
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值