并发编程(一)之创建线程和线程中的常用方法

本文详细介绍了Java中线程的概念,包括进程与线程的区别,Java中线程的创建方式,如使用Thread类、Runnable接口及FutureTask。此外,还探讨了线程的生命周期,常用方法如start()、run()、sleep()、join()、interrupt()的功能与注意事项,以及如何优雅地终止线程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 进程线程

1.1 进程

        程序由指令和数据组成。但这些指令要运行,数据要读写,就必须将指令加载至CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备,而进程就是用来加载指令、管理内存、管理IO的

程序和进程
        当一个程序被运行,从磁盘加载这个程序的代码至内存。这时,就开启了一个进程。进程(动态)可以视为程序(静态)的一个实例。大部分程序可以同时运行多个实例进程。如:记事本、浏览器;也有的程序只能启动一个实例进程。如:任务管理器

1.2 线程

        一个进程可以分为多个线程,而一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给CPU执行。

        在Java中,线程作为最小调度单位,进程作为资源分配的最小单位。

2. Java中的线程

在Java程序启动时,都会创建一个线程----Main线程。

2.1 创建并运行线程

如果你想在Main线程之外创建其他线程,可以用如下方法。

  1. Thread
  2. Runnable接口
  3. FutureTask

2.1.1 Thread

// 创建线程 (使用了匿名内部类)
Thread t1 = new Thread() {
    public void run() {
		// 要执行的任务
	}
}
// 启动线程
t1.start()

2.1.2 Runnable

Runnable r = new Runnable() {
	public void run() {
		// 要执行的任务
	}
}
// 创建线程对象
Thread t1 = new Thread(r, "t1");
// 启动线程
t1.start();

Java8 以后可以使用 Lambda 表达式来精简代码。如:

Runnable r = () -> {
	// 要执行的任务
}
// 创建线程对象
Thread t1 = new Thread(r, "t1");
// 启动线程
t1.start();

如果对 Lambda 表达式不了解的,可以看看这篇博客 手把手地带你走进Lambda表达式之门

2.1.3 FutureTask

FutureTask 能够接收 Callable 接口的参数,用来处理有返回结果的情况。

FutureTask<Integer> task = new FutureTask<>(() -> {
	// 要执行的任务
	return 666;
})
new Thread(task, "t1").start();

// 主线程阻塞,同步等待 task 执行完毕的结果
Integer result = task.get();

2.1.4 Thread与Runnable

查看 Thread 源码:

  1. Thread 实现了 Runnable 接口
  2. Thread 有一个 Runnable 类型的成员变量 target

查看 Thread 的构造方法:

public Thread(Runnable target) {
   init(null, target, "Thread-" + nextThreadNum(), 0);
}

最终会调用:

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
    ...
    this.target = target;
    ...
}

将构造方法中的参数传递给了成员变量 target。

查看 Thread 中的 run() 方法:

public void run() {
    if (target != null) {
        target.run();
    }
}

如果 target != null,则运行 Runnable 接口中的 run() 方法;否则,直接运行Thread 中的 run() 方法。

方法1 中的原理:
        通过 匿名内部类实现的方式可以看做是 Thread 的子类(亦或继承 Thread),子类重写了父类中的 run() 方法。所以,最终运行的是子类中的方法

方法2 中的原理:
        把 Runnable 类型的变量作为 Thread 的构造方法的参数,在此构造方法中,将此参数传递给成员变量 target,所以 target != null,最终会执行 Runnable 接口中的方法,而上述的 Lambda 表达式将 Runnable 接口中的 run() 方法进行重写,所以,最终会执行 Lambda表达式。

2.2 栈与栈帧

        JVM是由堆、栈、方法区等组成,其中,栈内存是给线程用的,每个线程启动后,JVM会为它分配一个栈内存。而每个栈是由多个栈帧组成,对应着每次方法调用时所占用的内存

2.3 上下文切换

因为以下原因导致CPU不再执行当前的线程,转而执行另一个线程的代码:

  1. 线程的CPU时间片用完
  2. 垃圾回收
  3. 有更优先级的线程需要执行
  4. 线程自己调用了sleep()、wait()、lock() 方法等

3. 常用的方法

方法名功能描述注意
start启动一个新的线程,在新的线程中运行 run() 方法start() 方法只是让线程进入就绪状态,里面的代码不一定就马上执行(CPU时间片还没分给它)。每个线程对象的 start() 方法只能调用一次,如果调用多次,会抛出异常IllegalThreadStateException
run新的线程启动后,会执行的代码
join等待线程运行结束
isInterrupted判断是否被打断不会清除打断标记
interrupt打断线程如果被打断的线程正在sleep、wait、join 会导致被打断的线程抛出异常 InterruptedException,并清除标记;如果打断正在运行的线程,则会设置打断标记;park 线程被打断,也会设置打断标记
interrupted判断当前线程是否被打断会清除打断标记
sleep(n)让当前执行的线程休眠 n 毫秒,休眠时间让出 CPU
yield提示线程调度器让出当前线程对 CPU 的使用

3.1 start()与run()

start() 方法表示启动一个新的线程,run() 方法表示线程启动后要执行的代码,那能否能直接调用 run() 方法?

public static void main(String[] args) {
    Thread t1 = new Thread() {
        @Override
        public void run () {
            System.out.println("hello world" + "=====" +Thread.currentThread().getName());
        }
    };
    t1.run();
}

在这里插入图片描述
run() 方法还是可以执行,但并没有创建新的线程,还是在 main 线程中执行的

3.2 sleep()

  1. 调用 sleep() 方法会让当前线程从 Running 进入 Timed Waiting 状态
  2. 其它线程可以使用 interrupt() 方法打断正在睡眠的线程,这时,sleep() 方法会抛出 InterruptedException异常
public static void main(String[] args) throws Exception{
    Thread t1 = new Thread() {
        @Override
        public void run () {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                System.out.println("wake up");
            }
        }
    };
    t1.start();
    Thread.sleep(1000);
    t1.interrupt();
}

在这里插入图片描述

3.3 join()

看下这段代码,打印 i 是什么?

static int i = 1;

private static void test() throws Exception{
    Thread t1 = new Thread(() -> {
        try {
            Thread.sleep(1000);
            i = 100;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    t1.start();
    System.out.println("i = " + i);
}

public static void main(String[] args) throws Exception{
    test();
}

在这里插入图片描述
         流程分析:main 线程启动,调用 test() 方法,开启一个新的线程 t1,main 和 t1 线程都是在同时运行的,main 线程会先执行语句 “System.out.println("i = " + i);”,只有当 t1 线程休眠1s后,才会将 i 赋值为 100。

那么,如何让打印的 i 为100呢?
只需要添加一个 join() 方法

t1.start();
t1.join();
System.out.println("i = " + i);

当 main 线程 执行到语句 “t1.join()”时,main 线程会一直等待着 t1 线程(t1 调用了 join()方法),直到 t1 线程运行结束后,main 线程才会继续执行。

3.4 interrupt()

3.4.1 打断阻塞的线程

打断 sleep、wait、join 的线程会清空打断标记。以 sleep 为例

public static void main(String[] args) throws Exception{
    Thread t1 = new Thread(() -> {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    t1.start();

    Thread.sleep(100);
    t1.interrupt();
    System.out.println("打断标记:" + t1.isInterrupted());
}

3.4.2 打断运行的线程

打断运行的线程是不会清除标记的

3.4.3 应用

两阶段终止模式
在线程 t1 中如何优雅地终止线程 t2?

        先看看两阶段终止模式的一个应用场景----做一个系统的健康监控,如:定时地去监控CPU 的使用率、内存的使用率等。可以使用一个后台的监控线程每隔2s不断地进行记录即可,当点击停止按钮时便不再监控了。

在这里插入图片描述
代码实现:

public class TwoPhraseTermination {
	// 监控线程
    private Thread monitor;

    // 启动监控线程
    public void start() {
        monitor = new Thread(() -> {
            while (true) {
                Thread current = Thread.currentThread();
                if (current.isInterrupted()) {
                    System.out.println("料理后事");
                    break;
                }
                try {
                    Thread.sleep(2000);
                    System.out.println("执行监控记录");
                } catch (InterruptedException e) {
                    // 重新设置打断标记
                    current.interrupt();
                }
            }
        });
        monitor.start();
    }

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

    public static void main(String[] args) throws Exception{
        TwoPhraseTermination termination = new TwoPhraseTermination();
        termination.start();
        Thread.sleep(5000);
        termination.stop();
    }
}

这就是实现了在一个 main 线程中优雅地终止了另一个线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值