Java SE 多线程的介绍及使用

2023-12-15 12:27:15

Java SE 多线程的介绍及使用

一 多线程的实现方式

1.继承Thread类

创建一个自定义类,继承 Thread 类,然后重写 run() 函数,在 run() 函数中实现业务逻辑。

// 创建自定义类
public class MyThread extends Thread{
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
            System.out.println("这是子线程呢");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

然后在 main() 方法中实例化该类的对象,最后通过 start() 方法运行子线程。

注意事项:不要将主线程的任务放在子线程之前,否则与单线程无异。

public static void main(String[] args) {
    MyThread myThread=new MyThread();
    myThread.start();
    System.out.println("这是主线程!");
}

优缺点总结:

优点,编码简单,易于理解多线程。

缺点,线程类继承了 Thread 类,无法再继承其他类,不利于程序的扩展。

2.实现Runnable接口

创建一个自定义类,实现 Runnable 接口,实现 run() 方法。在创建 Thread 实例时作为参数传递,然后运行Thread实例的start()方法。

// 创建一个自定类,并且实现 Runable 接口。
// 同时添加一个字段,用来接收另一个线程传递的参数值,并且在 run() 方法中输出。
public class MyThread2 implements Runnable{
    public MyThread2(String name){
        this.name=name;
    }
    private String name;

    @Override
    public void run() {
        System.out.println("这是使用另一个子线程的任务");
        System.out.println("name="+name);
    }
}

然后在主线程中创建该类的实例对象,并且将对象作为参数传递给 Thread ,然后运行 Thread 的 start() 方法。

public static void main(String[] args) {
    
    MyThread2 myThread2=new MyThread2("子线程2");
    
    Thread thread=new Thread(myThread2);
    thread.start();

    System.out.println("这是主线程!");
}

同时还可以使用匿名内部类的方式来实现。

public static void main(String[] args) {

    Thread thread=new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("这是匿名内部类的写法");
        }
    });

    thread.start();

    System.out.println("这是主线程");
}

优缺点总结:

优点,任务类只实现接口,可以继承其他类,实现其它接口,扩展性强,同时可以接收其他线程传递过来参数。

缺点,没缺点,只是多了一个Runnable 对象。

3.实现Callable接口

继承 Thread 类,和实现 Runnable 接口两种实现多线程的共同问题是,无法直接接收到线程的返回值。

具体实现过程:需要定义一个类,实现 Callable 接口,重写 call() 方法,封装要做的业务,定义要返回的数据。接着把Callable 对象封装成 FutureTask对象。

FutureTask对象才是真正的任务对象。最后将 FutureTask对象交给Thread对象,运行 Thread 对象的start() 即可。

// Callable 方法接收一个泛型类型,用来作为返回值的类型
public class MyThread3 implements Callable<Integer>{
    public MyThread3(Integer num1,Integer num2){
        this.num1=num1;
        this.num2=num2;
    }
    private Integer num1;
    private Integer num2;

    /**
     * 重写call方法,返回两束相加的值
     * @return
     */
    @Override
    public Integer call() throws InterruptedException {
        Thread.sleep(1000L);
        return num1+num2;
    }
}

然后在main函数中进行调用

public static void main(String[] args) throws ExecutionException, InterruptedException {

    MyThread3 myThread3=new MyThread3(4,4);
    FutureTask<Integer> futureTask=new FutureTask<>(myThread3);

    Thread thread=new Thread(futureTask);
    thread.start();

    System.out.println("这是主线程");
    // 该行代码在子线程任务执行完成时才会执行
    System.out.println("输出子线程的计算结果:"+futureTask.get());
}

4.线程Thread中的常用方法

sleep(),指定线程睡眠指定时间,单位为毫秒。

join(),等待当前线程执行完成后再执行后面的程序。

wait(),让线程进入等待,直到它被唤醒,或指定时间后。

二 线程安全问题

多个线程同时操作同一个共享资源的时候,可能会出现业务安全问题。

综合分析:1.多条线程同时执行;2.同时访问共享资源

1.线程同步

线程同步是解决线程安全问题的解决方案。

线程同步的思想:为对象加锁,保证每次只能有一个线程访问资源,等这个线程执行完毕再解锁,然后再放其他线程进来。

2.加锁方式1-同步代码块

将访问共享资源的代码给上锁,以此保证线程安全。

// 要上锁的对象object
// 底层会记住被加锁的代码块,如果已被加锁,则其他线程无法访问
synchronized (object) {
    // 核心业务代码
}

一般情况下,使用共享资源作为锁对象即可,实例方法使用 this ,静态方法时,可使用当前类的类对象加锁。保证加锁的对象在程序中只有一份即可。

原理:每次只允许一个线程加锁,执行完毕后自动解锁,其他线程才能进来执行。

注意事项:对于当前同步执行的线程来说,同步锁必须是同一把,否则会出bug。

3.加锁方式2-同步方法

把访问共享资源的核心方法给上锁,以此保证线程安全。

// 在方法上使用 synchronized 关键字
public synchronized void drawMoney(Double money) throws InterruptedException {
    // 核心代码
}

原理和同步代码块原理一样。

4.加锁方式3-Lock锁

通过Lock锁可以创建出一个锁对象,然后自行加锁和解锁,使用起来更加灵活,方便。

Lock锁是一个接口,不能直接实例化,可以采用它的实现类ReentrantLock类来构建Lock锁。

private Lock lock=new ReentrantLock();
public void functionName(Double money) {
    lock.lock();
    try {
        // 核心代码块
    } finally {
        lock.unlock();
    }
}

5.线程通信

当多个线程共同操作共享的资源时,线程之间可以通过某种方式相互告知自己的状态,相互协调,并避免无效的资源争夺。

线程通信常用模型:生产消费模型

三 其他相关内容

1.线程池

线程池是一个可以复用线程的技术。

ExecutorService 是JDK5.0开始提供的一个代表线程池的接口。

可以使用 ThreadPoolExecutor 创建线程池对象,也可以使用线程池工具类 Executors 来创建线程池对象。

2.并发和并行

进程:一个正在运行的程序,就是一个独立的进程。

线程是属于进程的,一个进程中可以运行很多个线程。

3.线程的生命周期

Java中,总共定义了6种线程的状态,可以在Thread类的枚举中看到。

以下是线程的6种状态及相互转换。

在这里插入图片描述

文章来源:https://blog.csdn.net/shaopengjie2/article/details/135010780
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。