Java

[Java]重学Java-Thread类和创建线程

2022-05-20  本文已影响0人  AbstractCulture

异步和同步

我们现在假设一个方法,需要新增一个用户,同时向用户发送一条短信.
同步的方式,就是新增用户和发送短线都是顺序执行,然后执行完才return;
异步的方式,就是新增完用户后直接返回了,发送短信这个动作是异步执行的,处理的时机是不确定的,由CPU决定。

实现多线程

继承Thread类

我们现在,通过继承Thread来实现多线程来达到异步执行任务的效果。

package com.tea.modules.java8.thread.create;

/**
 * 使用继承的方式实现多线程 <br>
 * 1. 通过JVM告诉操作系统创建线程 <br>
 * 2. 操作系统开辟内存并根据不同平台的创建线程函数创建线程对象 <br>
 * 3. 操作系统对线程对象进行调度,以确定执行时机 <br>
 * 4. 线程任务被执行,注意,异步执行并不保证先后顺序
 *
 * @author jaymin
 * @since 2021/9/4 0:21
 */
public class MyThread extends Thread {

    @Override
    public void run() {
        super.run();
        System.out.println(System.currentTimeMillis() + ":发送信息:" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        System.out.println(System.currentTimeMillis() + ":保存用户信息");
        MyThread myThread = new MyThread();
        // 注意,启动多线程用start而不是run
        // 调用start方法最终会调用run方法
        myThread.start();
    }
}

实现的方式很简单,直接继承Thread然后重写run方法即可。这里要注意,启动Thread任务是调用start,最终会回调触发我们的start方法.

实现Runnable接口

package com.tea.modules.java8.thread.create;

/**
 * 实现Runnable接口来实现多线程 <br>
 * 使用Runnable更加好,因为JAVA语言不支持多继承,使用接口可以让扩展性更加好. <br>
 *
 * @author jaymin
 * @since 2021/9/4 0:40
 */
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(System.currentTimeMillis() + "发送信息");
    }

    public static void main(String[] args) {
        System.out.println(System.currentTimeMillis() + ":保存用户信息");
        MyRunnable myRunnable = new MyRunnable();
        new Thread(myRunnable).start();
    }
}

实现Runnable接口,然后通过构造函数传入Thread中,也是可以达到异步的效果的。平时更加推荐这种做法,因为Java是单一继承特性,但是接口是可以多实现的,扩展性更好。

实现Callable接口

我们现在有了Runnable的方式去启动一个线程,但是这种方式无法有效地拿到返回值,如果我们希望拿到异步的处理结果(比如说异步去查询数据库的数据),那么我们需要Callable这种方式去实现。

package com.tea.modules.java8.thread.create;

import java.util.concurrent.*;

/**
 * com.tea.modules.java8.thread.create
 *
 * @author jaymin
 * @since 2022/4/29
 */
public class MyFuture implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println(System.currentTimeMillis() + ":发送短信");
        return "success";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        System.out.println(System.currentTimeMillis() + ":保存用户信息");
        MyFuture myFuture = new MyFuture();
        ExecutorService executorService = Executors.newCachedThreadPool();
        Future<String> future = executorService.submit(myFuture);
        String result = future.get();
        System.out.println(result);
        executorService.shutdown();
    }
}

通过实现Callable你可以向线程池提交你的Future,然后通过get方法获取返回值。
注意,这里的get过程是阻塞的.

将Runnable和Callable封装成FutureTask

FutureTask提供了更友好的方式支持异步,他可以包装你的Runnable和Callable任务,获取结果时我们只需要关注FutureTask就行了。

package com.tea.modules.java8.thread.create;

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

/**
 * com.tea.modules.java8.thread.create
 *
 * @author jaymin
 * @since 2022/4/29
 */
public class MyFutureTask {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyFuture myFuture = new MyFuture();
        FutureTask<String> futureTask = new FutureTask<>(myFuture);
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.submit(futureTask);
        String result = futureTask.get();
        System.out.println("callable:" + result);
        MyRunnable myRunnable = new MyRunnable();
        FutureTask<String> runnableTask = new FutureTask<>(myRunnable, "success");
        executorService.submit(runnableTask);
        System.out.println("runnable:" + runnableTask.get());
        executorService.shutdown();
    }
}
1651223699779:发送短信
callable:success
1651223699781:发送信息
runnable:success

CompletableFuture

java8提供了CompletableFuture让你能用流的方式处理异步,这里给个简单的演示,后续我会详细介绍这个类

package com.tea.modules.java8.thread.completable;

import com.tea.modules.model.po.User;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 * com.tea.modules.java8.thread.completable
 *
 * @author jaymin
 * @since 2022/4/29
 */
public class CompletableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        User user = User.builder().age(1).userName("张三").build();
        CompletableFuture<User> completableFuture = CompletableFuture.completedFuture(user)
                .thenApplyAsync(u -> {
                    System.out.println("保存信息");
                    return u;
                })
                .thenApplyAsync(u -> {
                    System.out.println("现在向" + u.getUserName() + "发送信息");
                    return u;
                });
        completableFuture.get();
        completableFuture.join();
    }
}
保存信息
现在向张三发送信息

Thread类

Thread就是多线程在Java语言中的体现,通过其提供的api我们可以去控制线程执行任务的状态.

start-启动线程

start是线程启动的方式,Thread类本身实现了Runnable接口,调用start会触发native方法并回调run方法。

interrupt-中断

注意,中断线程需要使用interrupt方法,调用此方法时,线程会被设置成中断状态,每个线程都有一个boolean属性,每个线程会时不时检查这个标志,以判断线程是否中断。
对一个阻塞状态的线程调用interrupt,会触发InterruptedException.

sleep-休眠

休眠给定的毫秒数,1000即为1秒

守护线程

t.setDaemon(true);

通过以上的方式可以将Thread对象设置为守护线程,守护线程可以理解成一个后台的任务,它负责为其他线程服务,当仅剩下守护线程的时候,JVM就会退出。

异步调用时出异常了怎么办

在异步执行任务的时候,其实你在外层去做try-catch是没用的

package com.tea.modules.java8.thread.create;

/**
 * 实现Runnable接口来实现多线程 <br>
 * 使用Runnable更加好,因为JAVA语言不支持多继承,使用接口可以让扩展性更加好. <br>
 *
 * @author jaymin
 * @since 2021/9/4 0:40
 */
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(System.currentTimeMillis() + ":发送信息");
        throw new RuntimeException("我其实报错了");
    }

    public static void main(String[] args) {
        System.out.println(System.currentTimeMillis() + ":保存用户信息");
        try {
            MyRunnable myRunnable = new MyRunnable();
            new Thread(myRunnable).start();
        } catch (Exception e) {
            System.out.println("线程产生了异常: " + e.getMessage());
            throw new RuntimeException(e);
        }
    }
}

这个时候我们需要为Thread注册一个异常处理器,注意他是全局生效。

package com.tea.modules.java8.thread.create;

/**
 * 实现Runnable接口来实现多线程 <br>
 * 使用Runnable更加好,因为JAVA语言不支持多继承,使用接口可以让扩展性更加好. <br>
 *
 * @author jaymin
 * @since 2021/9/4 0:40
 */
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(System.currentTimeMillis() + ":发送信息");
        throw new RuntimeException("我其实报错了");
    }

    public static void main(String[] args) {
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                //t参数接收发生异常的线程, e就是该线程中的异常
                System.out.println(t.getName() + "线程产生了异常: " + e.getMessage());
            }
        });
        System.out.println(System.currentTimeMillis() + ":保存用户信息");
        try {
            MyRunnable myRunnable = new MyRunnable();
            new Thread(myRunnable).start();
        } catch (Exception e) {
            System.out.println("线程产生了异常: " + e.getMessage());
            throw new RuntimeException(e);
        }
    }
}

总结

本文我向你展示了几种创建多线程的方法,总的来说,我更推荐使用FutureTask或者CompletableFuture的方式去实现异步。当然,如果在Spring框架下进行开发,我们可以依赖@Async来实现,只需要简单配置线程池即可。

上一篇下一篇

猜你喜欢

热点阅读