java多线程基础
前言
我们现代的CPU一般都是多核CPU,合理使用进程/线程可以更大程度上利用CPU的性能,并发编程也是一种常用的编程策略。相比于进程,线程的开销跟小更轻量,这也是java所推荐的方案。本文将从进程的概念谈起,介绍进程的创建以及简单的使用
什么是线程以及我们为什么需要线程
在前面,我们介绍过进程的概念,线程和进程的功能和概念几乎是相同的。进程的创建销毁各种开销是巨大的,线程则是一个轻量级进程,简单来说线程是一个执行流
线程是从属于进程的,一个进程可以拥有多个线程,这些线程会共享父进程的资源,这也是为什么线程比进程轻量的原因。前面我们介绍操作系统使用PCB描述进程再组织,这是建立在一个进程中只有一个线程中情况,一个进程多个线程则需要时使用多个PCB分表描述进程内的线程,这些线程分别独立调度。
但线程也并非越多越好,在极端的情况下,线程之间会竞争从而降低整体的效率。进程间是独立的,但是线程如果不妥善处理崩溃可能会导致整个进程崩溃
java和线程
线程是通过操作系统提供一组API进行操作,java为了自身跨平台的特性,封装了这些操作提供了一组java操作线程的API——Thread类
我们可以先简单见一见如何使用这个类创建线程
class MyThread extends Thread {
@Override
public void run() {
while (true) {
System.out.println("hello thread");
}
}
}
public class Demo1 {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
while (true) {
System.out.println("hello main");
}
}
}
继承Thread类,重run方法,这个方法代表这个线程要执行的代码逻辑,在主线程(main函数就是主线程)中创建这个自定义线程的对象,调用对象的start方法,这是系统会自动创建线程执行run里面的代码逻辑。如果是对象.run不是创建线程而是调用方法
我们可以通过jdk通过的jconsole观察线程的状态。这个程序位于jdk安装目录bin下,会描述所有java进程中的线程状态,包括进程名称,状态调用栈此类的信息。这可以方便我们后期对代码的调试
其他创建线程的方案
前面我们使用继承Thread的方式创建线程,除此之外我们在介绍几种常见的创建方案
实现Runnable
class RunnableDemo implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("hello thread");
}
}
}
public class Demo2 {
public static void main(String[] args) {
RunnableDemo runnableDemo = new RunnableDemo();
Thread thread = new Thread(runnableDemo);
thread.start();
while (true) {
System.out.println("hello main");
}
}
}
实现Runnable接口,创建Runnable对象,将对象传入到Thread中
注意 Runnable接口只是一种解耦合的方案,用来提取任务逻辑,具体的创建线程还是Thread实现的
匿名内部类
上面不管是对Thread类的继承还是对Runnable的实现都可以使用匿名内部类来实现简写
public class Demo3 {
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run() {
while (true){
System.out.println("hello thread");
}
}
};
thread.start();
while (true){
System.out.println("hello main");
}
}
}
public class Demo4 {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("Runnable");
}
}
};
Thread thread = new Thread(runnable);
thread.start();
while (true) {
System.out.println("main");
}
}
}
lambda
其中Runnable接口使用@FunctionalInterface注解声明为函数式接口,使用lambda表达式可以达到同样的效果
public class Deno5 {
public static void main(String[] args) {
Thread thread = new Thread(()->{
while (true){
System.out.println("hello thread");
}
});
thread.start();
while (true){
System.out.println("hello main");
}
}
}
Thread类常见方法属性
构造方法
常见的构造方法主要是以下四种
//无参构造 创建线程
Thread thread1 = new Thread();
//创建制定名称的线程
Thread thread2 = new Thread("name");
//传入 runnable接口
Thread thread3 = new Thread(()->{
while (true){
System.out.println("hello thread");
}
});
//传入runnable接口和线程名称
Thread thread4 = new Thread(new Runnable() {
@Override
public void run() {
while (true){
System.out.println("hello thread");
}
}
},"name");
属性
简单介绍一些常用的属性和设置/获取属性的方法
//声明线程
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("hello thread");
}
}
});
//启动线程
thread.start();
// 获取线程id
thread.getId();
// 获取线程名称
thread.getName();
// 设置线程名称
thread.setName("name");
// 获取线程状态 返回值是状态的枚举类
thread.getState();
// 获取线程优先级
thread.getPriority();
// 设置线程优先级
// 优先级范围是1-10
// 优先级越大,线程抢占cpu的概率越大
// 可以使用Thread.MAX_PRIORITY和Thread.MIN_PRIORITY这种常量来设置优先级
thread.setPriority(10);
// 判断线程是否存活
// 可能对象存在但是线程已经被销毁或者还未启动
thread.isAlive();
// 判断是否是后台线程(守护线程)
// 默认是前台线程,前台线程会一直运行,后台线程会在进程结束的时候结束
thread.isDaemon();
// 判断线程是否中断
thread.isInterrupted();
启动线程
前面我们介绍过,线程的启动必须调用start。run只是实现了线程需要执行的代码逻辑,但是线程的创建还是由start方法内部实现
中断线程
java中中断线程只能是让一个线程的run方法快速执行完成
我们先来看一段代码
public class Demo8 {
public static void main(String[] args) {
Thread thread = new Thread(()->{
while (true) {
System.out.println("hello thread");
}
});
thread.start();
while (true) {
System.out.println("hello main");
}
}
}
这里的thread线程是个前台死循环的线程,main线程想要中断这个thread线程应该如何操作呢
我们可以这样
public class Demo8 {
public static Boolean flag = true;
public static void main(String[] args) {
Thread thread = new Thread(()->{
while (flag) {
System.out.println("hello thread");
}
});
System.out.println("thread线程启动");
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = false;
System.out.println("thread线程中断");
}
}
注意,如果将flag声明为main的变量时,lambda虽然会自动捕获这个变量,但是要求这个变量是final修饰或者实际上是final的变量,我们这种写法显然是不够优雅的。并且如果线程休眠必须等待休眠结束才能中断线程。那么有没有办法可以优雅又可以在休眠中唤醒呢
可以使用Thread.interrupted() 或者 Thread.currentThread().isInterrupted()
Thread.currentThread()会得到当前线程的对象
public class Demo9 {
public static void main(String[] args) {
Thread thread = new Thread(()->{
while (!Thread.currentThread().isInterrupted()) {
System.out.println("hello thread");
}
});
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
thread.interrupt();
System.out.println("线程中断");
}
}
特别的如果线程正在sleep则会离开抛出一个InterruptedException,抛出错误的同时清除标志位
等待线程
有点时候某个线程需要等待另外一个线程的工作全部完成,才会执行自己的逻辑
可以使用join等待另外一个线程执行完成
public class Demo10 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("hello");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
try {
t.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("main");
}
}
注意这个A线程的这个方法在B线程中被调用则 B线程等待A线程调度完成才会继续向下执行
默认无参是死等的情况,也可以指定最大的等待时间
线程状态
线程状态在一个Thread.State的枚举类中 我们可以先答应看一下有哪些属性
public class Demo11 {
public static void main(String[] args) {
for (Thread.State state : Thread.State.values()) {
System.out.println(state);
}
}
}
运行结果:
NEW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED
NEW 对象存在但是没用调用start方法 线程还未被创建
TERMINATED 对象存在但是线程已经被销毁了
RUNNABLE 就绪状态 正在CPU上执行或者在CPU调度队列中
TIMED_WAITING 由sleep触发的固定时间的阻塞
WAITING 由wait这种不确定时间的阻塞
BLOCKED 由于锁竞争导致的阻塞
结语
以上便是今天的全部内容。如果有帮助到你,请给我一个免费的赞。
因为这对我很重要。
编程世界的小比特,希望与大家一起无限进步。
感谢阅读!