Java多线程——线程基础

前言

线程是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. 终止:代码执行完毕后,释放所有资源,线程进入终止状态

在这里插入图片描述

四、线程常用方法

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多线程编程的基础。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值