Java多线程——线程基础

  • 前言
  • 一、进程和线程
  • 二、Java创建线程
  • 三、线程状态
  • 3.1 五种状态
  • 3.2 六种状态
  • 四、线程常用方法
  • 4.1 start和run方法
  • 4.2 sleep和yield方法
  • 4.3 join方法
  • 4.4 interrupt方法
  • 4.5 守护线程
  • 总结


前言

线程是Java中不可或缺的重要功能,它们能使复杂的异步代码变得更简单,从而极大地简化了复杂系统的开发。此外,要想充分发挥多处理器系统的强大计算能力,最简单的方式就是使用多线程。随着现在硬件中处理器数量的持续增长,高效地编写并发程序变得越来越重要。

在学习多线程中,发现B站上黑马程序员讲解多线程的视频非常棒,推荐给大家:

一、进程和线程

进程和线程是老生常谈的一个问题,进程是资源分配的基本单位,线程是CPU调度的基本单位,每一个程序就是一个进程,而一个进程里面会包含多个线程。关于进程和线程的更多介绍可以参考进程和线程

二、Java创建线程

在Java中创建线程有三种方式:

  • 方法一:直接使用Thread
@Slf4j
public class Demo01 {
    public static void main(String[] args) {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                log.info("This is thread t1");
            }
        };
        t1.start();
    }
}

这种方法耦合度比较高,每个Thread都要重写里面的run方法

  • 方法二:使用Runnable配合Thread

线程内容分割开来
Runnable表示可运行的内容
Thread表示线程

@Slf4j
public class Demo01 {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                log.info("This is thread t2");
            }
        };
        new Thread(runnable).start();
    }
}
  • 方法三:使用FutureTask配合Thread
    FutureTask
    可以看到FutureTask类实现了Runnable接口,FutureTask能够接受Callable参数,从而获得线程返回值
// Callable是一个函数式接口
@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}
@Slf4j
public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> task = new FutureTask<>(() -> {
            log.info("This is futureTask");
            return 46;
        });
        new Thread(task).start();

        Integer result = task.get();
        log.info("result: {}", result.toString());
    }
}

三、线程状态

线程状态是线程的重要属性,Java线程状态一般有两种划分方法:五种和六种

3.1 五种状态

线程的五种状态:

  1. 新建:刚刚创建出来的线程的状态
  2. 就绪:线程分到资源,等待CPU的调用,但还没运行
  3. 运行:就绪的线程分到了CPU,正在运行
  4. 阻塞:线程没有分到资源运行代码,进入阻塞状态
  5. 终结:线程异常终端或者全部执行完毕

3.2 六种状态

五种状态是在操作系统的层次进行定义的,而且JDK中对线程状态的定义

// 线程状态类
public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }
  1. 新建:使用new关键字创建了一个thread对象
  2. 运行:包含正在运行的线程和等待CPU调度的线程
  3. 阻塞:在运行状态争夺锁失败的线程会进入阻塞状态
  4. 等待:在运行状态争夺锁成功,但资源不满足,主动放弃锁进入等待状态
  5. 超时等待:在等待时有一定时限,超过这个时间进入可运行状态
  6. 终止:代码执行完毕后,释放所有资源,线程进入终止状态

java 线程难不难学_java 线程难不难学

四、线程常用方法

Thread类中有许多常用方法:

方法

用处

start()

启动一个新线程,start方法会让线程进入就绪状态,还需要等待CPU分配时间片才可以运行

join()

等待线程运行结束

join(long n)

等待线程运行结束,最多等待n毫秒

getId()

获取线程Id

getName()

获取线程名称

setName()

获取线程名称

getPriority()

获取线程权限

setPriority()

设置线程权限

getState()

获取线程状态

isInterrupted()

判断是否被打断,不会清除打断标记

isAlive()

线程还是否存活

interrupt()

打断线程

interrupted()

判断当前线程是否被打断,会清除打断标记

currentThread()

获取当前正在执行的线程

sleep(long n)

线程休眠n毫秒

yield()

提醒线程调度器让出当前线程对CPU的使用

4.1 start和run方法

  • 直接调用run是在主线程中执行run方法,没有启动新的线程
  • 使用start是启动了新的线程,通过新的线程间接执行run中的代码

直接调用run

@Slf4j
public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.info("hello run");
            }
        };

        t1.run();
    }
    /**
     * 2022-11-25 11:06:41.553 INFO [main] - hello run
     */
}

调用start方法

@Slf4j
public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.info("hello run");
            }
        };

        t1.start();
    }
    /**
     * 2022-11-25 11:08:06.065 INFO [t1] - hello run
     */
}

4.2 sleep和yield方法

sleep

  • 调用sleep会让当前线程从Running进入Timed Waiting状态
  • 其他线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出InterruptedException
  • 睡眠结束后的线程也不一定会立刻得到执行

yield

  • yield方法可以让当前线程让出cpu,当前线程从Running状态进入Runnable就绪状态

线程优先级

  • 线程可以设置执行优先级,优先级会在线程调度的时候提示调度器
public final static int MIN_PRIORITY = 1;
    public final static int NORM_PRIORITY = 5;
    public final static int MAX_PRIORITY = 10;
  • JDK中优先级范围1-10,默认优先级为5

4.3 join方法

当前线程需要等待其他线程执行时,可以调用join方法

@Slf4j
public class Demo02 {
    public static void main(String[] args) throws InterruptedException {
        log.info("主线程开始");
        Thread t1 = new Thread(() -> {
            log.info("t1线程开始");
            // 线程睡眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log.info("t1线程执行结束");
        }, "t1");

        t1.start();
        // 等待t1线程执行结束
        t1.join();
        log.info("主线程结束");
    }
    /**
     * 2022-11-25 11:24:18.101 INFO [main] - 主线程开始
     * 2022-11-25 11:24:18.206 INFO [t1] - t1线程开始
     * 2022-11-25 11:24:19.212 INFO [t1] - t1线程执行结束
     * 2022-11-25 11:24:19.213 INFO [main] - 主线程结束
     */
}

4.4 interrupt方法

打断sleepwaitjoin的线程

@Slf4j
public class Demo03 {
    private static void test01() {
        Thread t1 = new Thread(() -> {
            log.info("t1线程开始");
            // 线程睡眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                log.info("线程被打断");
            }
            log.info("t1线程执行结束");
        }, "t1");

        t1.start();
        t1.interrupt();
        log.info("打断: {}", t1.interrupted());
    }

    /**
     * 2022-11-25 13:01:09.105 INFO [t1] - t1线程开始
     * 2022-11-25 13:01:09.109 INFO [t1] - 线程被打断
     * 2022-11-25 13:01:09.109 INFO [t1] - t1线程执行结束
     * 2022-11-25 13:01:09.113 INFO [main] - 打断: false
     */

    private static void test02() {
        Thread t2 = new Thread(() -> {
            log.info("t2线程开始");
            while (true) {
                Thread thread = Thread.currentThread();
                boolean interrupted = thread.interrupted();
                if (interrupted) {
                    log.info("线程打断状态: {}", interrupted);
                }
            }
        }, "t2");

        t2.start();
        t2.interrupt();
    }
    /**
     * 2022-11-25 13:00:42.959 INFO [t2] - t2线程开始
     * 2022-11-25 13:00:42.969 INFO [t2] - 线程打断状态: true
     */
}

应用:两阶段终止
即在一个线程中终止另一个线程,并给另一个线程释放资源或保存数据的时间

@Slf4j
public class TPTInterrupt {
    private Thread thread;

    public void start() {
        thread = new Thread(() -> {
            while (true) {
                Thread current = Thread.currentThread();
                if (current.isInterrupted()) {
                    log.info("释放资源");
                    break;
                }
                try {
                    Thread.sleep(1000);
                    log.info("将结果保存");
                } catch (InterruptedException e) {
                    // 在sleep阶段被打断时候,会清除打断标记,需要再次打断
                    current.interrupt();
                    log.info("线程打断");
                }
            }
        }, "监控线程");
        thread.start();
    }

    public void stop() {
        thread.interrupt();
    }

    public static void main(String[] args) throws InterruptedException {
        TPTInterrupt t = new TPTInterrupt();
        t.start();
        Thread.sleep(3500);
        log.info("stop");
        t.stop();
    }
}

4.5 守护线程

守护线程是操作系统中的一个概念,又被称为“服务线程”或“后台线程”,是指在程序运行时在后台提供一种通用服务的线程,如JVM中的垃圾清理就是一个守护线程。

守护线程用户线程的一个重要区别是:当用户线程没有全部运行结束时,程序不会停止;当用户线程全部运行完,只剩守护线程时,程序会终止。

在Java中可以通过thread.isDaemon()来判断线程是否是守护线程,并可以通过thread.setDaemon(true)来将线程设置为守护线程。

总结

本文简略介绍了线程的基础知识,这是学习Java多线程编程的基础。