并发编程——2.Java 线程

news2024/9/25 1:14:03

目录

  • 2.Java 线程
    • 2.1.创建和运行线程
      • 2.1.1.方法一:直接使用 Thread 类
      • 2.1.2.方法二:使用 Runnable 接口配合 Thread
      • 2.1.3.方法三:使用 FutureTask 配合 Thread
    • 2.2.观察多个线程同时运行
    • 2.3.查看进程线程的方法
    • 2.4.原理之线程运行
    • 2.5.线程的常见方法
      • 2.5.1.start 与 run
      • 2.5.2.sleep 与 yield
      • 2.5.3.join
      • 2.5.4.interrupt
    • 2.6.过时方法(不推荐使用)
    • 2.7.主线程与守护线程
    • 2.8.线程状态
      • 2.8.1.五种状态
      • 2.8.2.六种状态
    • 2.9.多线程应用——统筹(烧水泡茶)

本文笔记整理来自黑马视频https://www.bilibili.com/video/BV16J411h7Rd/?p=1,相关资料可在视频评论区进行获取。

2.Java 线程

2.1.创建和运行线程

2.1.1.方法一:直接使用 Thread 类

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test1")
public class Test1 {
    
    public static void main(String[] args) {
    	//创建线程对象
        Thread t = new Thread(() -> {
        	//要执行的任务
            log.debug("running");
        });
        //为线程设置名称,也可以在创建线程时直接指定,例如 Thread t1 = new Thread("t1") {...};
        t.setName("t1");
        //启动线程
        t.start();
        
        log.debug("running");
        //注:上面两条日志打印语句的顺序不固定
    }
}

某次运行结果如下:

16:08:10 [main] c.Test1 - running
16:08:10 [t1] c.Test1 - running

Process finished with exit code 0

2.1.2.方法二:使用 Runnable 接口配合 Thread

(1)把【线程】和【任务】(即要执行的代码)分开:

  • Thread 代表线程;
  • Runnable 可运行的任务(线程要执行的代码);
package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test1")
public class Test1 {
    public static void main(String[] args) {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                log.debug("running");
            }
        };
        
        Thread t = new Thread(r, "t1");
        t.start();
        
        log.debug("running");
    }
}

某次运行结果如下:

16:08:54 [t1] c.Test2 - running
16:08:54 [main] c.Test2 - running

Process finished with exit code 0

(2)Java 8 以后可以使用 lambda 精简代码(本代码中的前提是接口中只有一个抽象方法)。

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

public class Test1 {
    public static void main(String[] args) {
        Runnable r = () -> {
            log.debug("running");
        };
        Thread t = new Thread(r, "t1");
        t.start();
        log.debug("running");
    }
}

(3)原理之 Thread 与 Runnable 的关系
分析 Thread 的源码,理清它与 Runnable 的关系:

  • 方法一是把线程和任务合并在了一起, 方法二是把线程和任务分开了;
  • 用 Runnable 更容易与线程池等高级 API 配合;
  • 用 Runnable 让任务类脱离了 Thread 继承体系,更灵活;

2.1.3.方法三:使用 FutureTask 配合 Thread

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

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

@Slf4j(topic = "c.Test2")
public class Test2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                log.debug("running...");
                //线程睡眠 1000 ms
                Thread.sleep(1000);
                return 100;
            }
        });
        
        Thread t1 = new Thread(task, "t1");
        t1.start();
        
        log.debug("线程 {} 的返回结果为:{}", t1.getName(), task.get());
    }
}

结果如下:

20:53:04 [t1] c.Test2 - running...
20:53:06 [main] c.Test2 - 线程 t1 的返回结果为:100

Process finished with exit code 0

2.2.观察多个线程同时运行

通过下面的示例来理解:
① 交替执行;
② 谁先谁后,不由我们控制;

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test3")
public class Test3{
    public static void main(String[] args) {
        new Thread(() -> {
           while (true) {
               log.debug("running");
           }
        }, "t1").start();
    
        new Thread(() -> {
            while (true) {
                log.debug("running");
            }
        }, "t2").start();
    }
}

部分结果如下:

20:57:54 [t2] c.TestMultiThread - running
20:57:54 [t2] c.TestMultiThread - running
20:57:54 [t2] c.TestMultiThread - running
20:57:54 [t1] c.TestMultiThread - running
20:57:54 [t1] c.TestMultiThread - running
20:57:54 [t2] c.TestMultiThread - running
20:57:54 [t2] c.TestMultiThread - running
20:57:54 [t2] c.TestMultiThread - running
20:57:54 [t1] c.TestMultiThread - running
20:57:54 [t1] c.TestMultiThread - running
20:57:54 [t1] c.TestMultiThread - running
20:57:54 [t1] c.TestMultiThread - running
...

2.3.查看进程线程的方法

(1)Windows
① 任务管理器可以查看进程和线程数,也可以用来杀死进程;
② tasklist 查看进程;

在这里插入图片描述

③ taskkill 杀死进程;

在这里插入图片描述

(2)Linux

  • ps -fe 查看所有进程;
  • ps -fT -p 查看某个进程 (PID) 的所有线程;
  • kill 杀死进程;
  • top 按大写 H 切换是否显示线程;
  • top -H -p 查看某个进程 (PID) 的所有线程;

(3)Java

  • jps 命令查看所有 Java 进程;
  • jstack 查看某个 Java 进程(PID)的所有线程状态;
  • jconsole 来查看某个 Java 进程中线程的运行情况(图形界面);

① jconsole 远程监控配置
需要以如下方式运行你的 java 类:

java -Djava.rmi.server.hostname=`ip地址` -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=`连接端口` -Dcom.sun.management.jmxremote.ssl=是否安全连接 -Dcom.sun.management.jmxremote.authenticate=是否认证 java类

修改 /etc/hosts 文件将 127.0.0.1 映射至主机名;

如果要认证访问,还需要做如下步骤:

  • 复制 jmxremote.password 文件;
  • 修改 jmxremote.password 和 jmxremote.access 文件的权限为 600 即文件所有者可读写;
  • 连接时填入 controlRole(用户名),R&D(密码);

② jconsole 本地监控

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.4.原理之线程运行

(1)栈与栈帧
Java Virtual Machine Stacks 即 Java 虚拟机栈。我们都知道 JVM 中由堆、栈、方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟机就会为其分配一块栈内存。

  • 每个栈由多个栈帧 (Frame) 组成,对应着每次方法调用时所占用的内存;
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法;

在这里插入图片描述

(2)线程上下文切换 (Thread Context Switch)
因为以下一些原因导致 CPU 不再执行当前的线程,转而执行另一个线程的代码:

  • 线程的 cpu 时间片用完;
  • 垃圾回收;
  • 有更高优先级的线程需要运行;
  • 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法;

当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器 (Program Counter Register),它的作用是记住下一条 JVM 指令的执行地址,是线程私有的。

  • 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等;
  • Context Switch 频繁发生会影响性能;

2.5.线程的常见方法

方法名static功能说明注意
start()启动一个新线程,在新的线程运行 run 方法中的代码start 方法只是让线程进入就绪,里面代码不一定立刻运行(CPU 的时间片还没分给它)。每个线程对象的 start 方法只能调用一次,如果调用了多次会出现 IllegalThreadStateException
run()新线程启动后会调用的方法如果在构造 Thread 对象时传递了 Runnable 参数,则线程启动后会调用 Runnable 中的 run 方法,否则默认不执行任何操作。但可以创建 Thread 的子类对象,来覆盖默认行为
join()等待线程运行结束
join(long n)等待线程运行结束,最多等待 n 毫秒
getId()获取线程长整型的 idid 唯一
getName()获取线程名
setName(String)修改线程名
getPriority()获取线程优先级
setPriority(int)修改线程优先级Java中规定线程优先级是1~10 的整数,较大的优先级能提高该线程被 CPU 调度的机率
getState()获取线程状态Java 中线程状态是用 6 个 enum 表示,分别为:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED
isInterrupted()判断线程是否被打断不会清除打断标记
isAlive()线程是否存活(还没有运行完毕)
interrupt()打断线程如果被打断线程正在 sleep,wait,join 会导致被打断的线程抛出 InterruptedException,并清除打断标记;如果打断的正在运行的线程,则会设置打断标记 ;park 的线程被打断,也会设置打断标记
interrupted()static判断当前线程是否被打断会清除打断标记,即会将打断标记从 true 变为 false
currentThread()static获取当前正在执行的线程
sleep(long n)static让当前执行的线程休眠 n 毫秒,休眠时让出 CPU 的时间片给其它线程
yield()static提示线程调度器让出当前线程对CPU的使用主要是为了测试和调试

2.5.1.start 与 run

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

2.5.2.sleep 与 yield

(1)sleep

  • 调用 sleep 会让当前线程从 Running 状态进入 Timed Waiting 状态(阻塞);
  • 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException;
  • 睡眠结束后的线程未必会立刻得到执行;
  • 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性;
package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test7")
public class Test6 {
    public static void main(String[] args) {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
    
        t1.start();
        log.debug("t1 线程的状态是:{}", t1.getState());
    
        try {
            //主线程休眠 500 ms
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    
        log.debug("t1 线程的状态是:{}", t1.getState());
    }
}

结果如下:

15:47:19 [main] c.Test6 - t1 线程的状态是:RUNNABLE
15:47:19 [main] c.Test6 - t1 线程的状态是:TIMED_WAITING

Process finished with exit code 0
package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test7")
public class Test7 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    log.debug("wake up...");
                    e.printStackTrace();
                }
            }
        };
        t1.start();
        
        //主线程休眠 500 ms
        Thread.sleep(1000);
        log.debug("interrupt...");
        t1.interrupt();
    }
}

结果如下:

16:04:06 [main] c.Test6 - interrupt...
16:04:06 [t1] c.Test6 - wake up...
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at cn.itcast.test.Test6$1.run(Test6.java:12)

Process finished with exit code 0

(2)yield

  • 调用 yield 会让当前线程从 Running 状态进入 Runnable 就绪状态,然后调度执行其它线程;
  • 具体的实现依赖于操作系统的任务调度器

(3)线程优先级

  • 线程优先级(1~10之间的整数,默认是 5)会提示 (hint) 调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它;
  • 如果 CPU 比较忙,那么优先级高的线程会获得更多的时间片,但 CPU 空闲时,优先级几乎没作用;

(4)应用:限制对 CPU 的使用
在没有利用 CPU 来计算时,不要让 while(true) 空转浪费 CPU,这时可以使用 yield 或 sleep 来让出 CPU 的使用权给其他程序

while(true) {
    try {
        Thread.sleep(50);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
  • 可以用 wait 或条件变量达到类似的效果;
  • 不同的是,后两种都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景;
  • sleep 适用于无需锁同步的场景;

wait 实现

synchronized(锁对象) {
    while(条件不满足) {
        try {
            锁对象.wait();
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
    }
    // do sth...
}

条件变量实现

lock.lock();
try {
    while(条件不满足) {
        try {
            条件变量.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    // do sth...
} finally {
    lock.unlock();
}

2.5.3.join

(1)下面的代码执行,打印的 r 值是多少?

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

import static java.lang.Thread.sleep;

@Slf4j(topic = "c.Test10")
public class Test10 {
    static int r = 0;
    
    public static void main(String[] args) throws InterruptedException {
        test1();
    }
    
    private static void test1() {
        log.debug("开始");
        Thread t1 = new Thread(() -> {
            log.debug("开始");
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("结束");
            r = 10;
        });
        t1.start();
        log.debug("结果为:{}", r);
        log.debug("结束");
    }
}

(2)分析

  • 因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 r=10;
  • 而主线程一开始就要打印 r 的结果,所以只能打印出 r=0;

(3)解决方法

  • 用 sleep 方法行不行?为什么?不推荐使用 sleep 方法,因为我们不能确切地知道 t1 线程需要多少时间才能执行结束。
  • 用 join 方法,加在 t1.start() 之后即可。

(4)应用之同步
以调用方角度来讲,如果

  • 需要等待结果返回,才能继续运行就是同步
  • 不需要等待结果返回,就能继续运行就是异步

在这里插入图片描述

① 等待多个结果
下面代码的 cost 大约为多少秒?

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.TestJoin")
public class TestJoin {
    static int r1 = 0;
    static int r2 = 0;
    
    public static void main(String[] args) throws InterruptedException {
        test2();
    }
    
    private static void test2() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
               TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r1 = 10;
        });
        Thread t2 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r2 = 20;
        });
        t1.start();
        t2.start();
        long start = System.currentTimeMillis();
        log.debug("join begin");
        t1.join();
        log.debug("t1 join end");
        t2.join();
        log.debug("t2 join end");
        long end = System.currentTimeMillis();
        log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
    }
}

分析如下:

  • 第一个 join:等待 t1 时,t2 并没有停止,而在运行;
  • 第二个 join:1s 后, 执行到此,t2 也运行了 1s,因此也只需再等待 1s;

如果颠倒两个 join 呢?最终结果都为 2s 多。

在这里插入图片描述

② 有时效的 join

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.TestJoin")
public class TestJoin {
    static int r1 = 0;
    static int r2 = 0;
    public static void main(String[] args) throws InterruptedException {
        test3();
    }
    public static void test3() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r1 = 10;
        });
        long start = System.currentTimeMillis();
        t1.start();
        //等够时间:t1 线程提前执行结束会导致 join 结束,r = 10
        t1.join(1500);
        //没等够时间:就不再等待 t1,r = 0
        //t1.join(1500);
        long end = System.currentTimeMillis();
        log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
    }
}

2.5.4.interrupt

(1)打断 sleep、wait、join 的线程
这几个方法都会让线程进入阻塞状态,打断 sleep 的线程,会清空打断状态,以 sleep 为例:

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test11")
public class Test11 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("sleep...");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");
        t1.start();
        Thread.sleep(1000);
        log.debug("interrupt");
        t1.interrupt();
        log.debug("打断标记:{}", t1.isInterrupted());
    }
}

结果如下:

22:31:20 [t1] c.Test11 - sleep...
22:31:21 [main] c.Test11 - interrupt
22:31:21 [main] c.Test11 - 打断标记:false
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at cn.itcast.test.Test11.lambda$main$0(Test11.java:11)
	at java.lang.Thread.run(Thread.java:748)

Process finished with exit code 0

(2)打断正常运行的线程
打断正常运行的线程,不会清空打断状态,即打断标记还是会从 false 变为 true。

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test12")
public class Test12 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (true) {
                boolean interrupted = Thread.currentThread().isInterrupted();
                if (interrupted) {
                    log.debug("当前线程被打断了,退出循环");
                    break;
                }
            }
        }, "t1");
        t1.start();
        
        Thread.sleep(1000);
        log.debug("interrupt");
        t1.interrupt();
    }
}

结果如下:

22:44:19 [main] c.Test12 - interrupt
22:44:19 [t1] c.Test12 - 当前线程被打断了,退出循环

Process finished with exit code 0

(3)两阶段终止模式

在这里插入图片描述

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.Test13")
public class Test13 {
    public static void main(String[] args) throws InterruptedException {
        TwoPhaseTermination tpt = new TwoPhaseTermination();
        tpt.start();
        
        Thread.sleep(3500);
        tpt.stop();
    }
}

@Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination {
    private Thread monitor;
    
    //启动监控线程
    public void start() {
        monitor = new Thread(() -> {
            while (true) {
                Thread curThread = Thread.currentThread();
                if (curThread.isInterrupted()) {
                    log.debug("处理后事");
                    break;
                }
                try {
                    TimeUnit.SECONDS.sleep(1);
                    log.debug("执行监控记录");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    //重新设置打算标记
                    curThread.interrupt();
                }
            }
        });
    
        monitor.start();
    }
    
    //停止监控线程
    public void stop() {
        monitor.interrupt();
    }
}

输出如下:

10:45:59 [Thread-0] c.TwoPhaseTermination - 执行监控记录
10:46:00 [Thread-0] c.TwoPhaseTermination - 执行监控记录
10:46:01 [Thread-0] c.TwoPhaseTermination - 执行监控记录
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at cn.itcast.test.TwoPhaseTermination.lambda$start$0(Test13.java:32)
	at java.lang.Thread.run(Thread.java:748)
10:46:01 [Thread-0] c.TwoPhaseTermination - 处理后事

Process finished with exit code 0

(4)打断 park 线程

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

@Slf4j(topic = "c.Test14")
public class Test14 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("park");
            //阻塞当前线程
            LockSupport.park();
            log.debug("unpark");
            log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
        }, "t1");
    
        t1.start();
    
        TimeUnit.SECONDS.sleep(1);
        
        //打断 park 线程
        t1.interrupt();
    }
}

结果如下:

10:56:45 [t1] c.Test14 - park
10:56:46 [t1] c.Test14 - unpark
10:56:46 [t1] c.Test14 - 打断状态:true

Process finished with exit code 0

注意:执行 t1.interrupt() 后,打断标记为 true,如果再想使用 park() 来阻塞线程,是不会产生效果的,因此,如果想让 park() 再次生效,需要清除打断标记(即将其从 true 置为 false),可以使用静态方法 Thread.interrupted() 来进行清除。

2.6.过时方法(不推荐使用)

下面是一些不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁。

方法名功能说明
stop()停止线程运行
suspend()挂起(暂停)线程运行
resume()恢复线程运行

2.7.主线程与守护线程

默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.Test15")
public class Test15 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    break;
                }
            }
            log.debug("结束");
        }, "t1");
        //将线程 t1 设置为守护线程(需要在 start 之前设置)
        t1.setDaemon(true);
        t1.start();
    
        TimeUnit.SECONDS.sleep(1);
        log.debug("结束");
    }
}

输出如下:

11:17:27 [main] c.Test15 - 结束

Process finished with exit code 0

注意:

  • 垃圾回收器线程就是一种守护线程;
  • Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求;

2.8.线程状态

2.8.1.五种状态

这是从操作系统层面来描述的。

在这里插入图片描述

  • 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联;
  • 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行;
  • 【运行状态】指获取了 CPU 时间片运行中的状态,当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
  • 【阻塞状态】
    • 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【阻塞状态】
    • 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】;
    • 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们;
  • 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态;

2.8.2.六种状态

(1)这是从 Java API 层面来描述的,根据 Thread.State 枚举,分为六种状态。

在这里插入图片描述

  • NEW 线程刚被创建,但是还没有调用 start() 方法
  • RUNNABLE 当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行);
  • BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分,后面会在状态转换一节详述;
  • TERMINATED 当线程代码运行结束;

(2)六种状态演示

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.TestState")
public class TestState {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("running");
        }, "t1");
        
        Thread t2 = new Thread(() -> {
            while (true) {
            
            }
        }, "t2");
        t2.start();

        Thread t3 = new Thread(() -> {
            log.debug("running");
        }, "t3");
        t3.start();
        
        Thread t4 = new Thread(() -> {
            synchronized (TestState.class) {
                try {
                    TimeUnit.SECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t4");
        t4.start();
        
        Thread t5 = new Thread(() -> {
            try {
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t5");
        t5.start();
        
        Thread t6 = new Thread(() -> {
            synchronized (TestState.class) {
                try {
                    TimeUnit.SECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t6");
        t6.start();
        
        TimeUnit.SECONDS.sleep(1);
        
        log.debug("t1 state: {}", t1.getState());
        log.debug("t2 state: {}", t2.getState());
        log.debug("t3 state: {}", t3.getState());
        log.debug("t4 state: {}", t4.getState());
        log.debug("t5 state: {}", t5.getState());
        log.debug("t6 state: {}", t6.getState());
    }
}

输出如下:

15:41:33 [t3] c.TestState - running
15:41:34 [main] c.TestState - t1 state: NEW
15:41:34 [main] c.TestState - t2 state: RUNNABLE
15:41:34 [main] c.TestState - t3 state: TERMINATED
15:41:34 [main] c.TestState - t4 state: TIMED_WAITING
15:41:34 [main] c.TestState - t5 state: WAITING
15:41:34 [main] c.TestState - t6 state: BLOCKED

2.9.多线程应用——统筹(烧水泡茶)

在这里插入图片描述

(1)解法 1:join

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.Test16")
public class Test16 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("洗水壶");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("烧开水");
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");
    
        Thread t2 = new Thread(() -> {
            log.debug("洗茶壶");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("洗茶叶");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("拿茶叶");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("泡茶");
        }, "t2");
        
        t1.start();
        t2.start();
    }
}

输出如下:

16:06:55 [t2] c.Test16 - 洗茶壶
16:06:55 [t1] c.Test16 - 洗水壶
16:06:56 [t1] c.Test16 - 烧开水
16:06:56 [t2] c.Test16 - 洗茶叶
16:06:58 [t2] c.Test16 - 拿茶叶
16:07:01 [t2] c.Test16 - 泡茶

Process finished with exit code 0

解法 1 的缺陷:

  • 上面模拟的是小王等老王的水烧开了,小王泡茶,如果反过来要实现老王等小王的茶叶拿来了,老王泡茶呢?代码最好能适应两种情况。
  • 上面的两个线程其实是各执行各的,如果要模拟老王把水壶交给小王泡茶,或模拟小王把茶叶交给老王泡茶呢?

上述问题,我们在后面会逐步处理。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/130189.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

React学习06-React Router 6

React Router 6 概述 React Router 以三个不同的包发布到 npm 上&#xff0c;它们分别为&#xff1a; react-router: 路由的核心库&#xff0c;提供了很多的&#xff1a;组件、钩子。react-router-dom: 包含react-router所有内容&#xff0c;并添加一些专门用于 DOM 的组件&…

Elasticsearch处理表关联关系的N种方式

Elasticsearch处理表关联关系是比较复杂的问题&#xff0c;处理不好会出现性能问题、数据一致性问题等&#xff1b; 今天我们特意分享一下几种方式&#xff0c;对象类型&#xff08;宽表&#xff09;、嵌套类型、父子关联关系、应用端关联&#xff0c;每种方式都有特定的业务需…

CycloneDDS(3)安全Security

本规范定义了符合DDS实现的安全模型和服务插件接口(SPI)架构。DDS安全模型通过DDS实现调用这些SPI来实现。 构成DDS安全模型的三个插件是: 1、身份验证服务插件 提供验证调用DDS操作的应用程序和/或用户身份的方法。包括在参与者之间执行相互身份验证和建立共享秘密的设施…

深度学习模型训练的tricks总结

学习率角度 学习率是一个非常非常重要的超参数&#xff0c;这个参数呢&#xff0c;面对不同规模、不同batch-size、不同优化方式、不同数据集&#xff0c;其最合适的值都是不确定的&#xff0c;我们无法光凭经验来准确地确定lr的值&#xff0c;我们唯一可以做的&#xff0c;就…

从零开始配置vim(30)——DAP的其他配置

很抱歉这么久才来更新这一系列&#xff0c;主要是来新公司还在试用期&#xff0c;我希望在试用期干出点事来&#xff0c;所以摸鱼的时间就少了。加上前面自己阳了休息了一段时间。在想起来更新就过去一个多月了。废话不多说了&#xff0c;让我们开始进入正题。 在前一章&#…

JUC 并发进阶学习(一)

该学习笔记是本人依据相关的学习视频整体汇总&#xff0c;相关的视频学习可以自己去搜看看。 【狂神说Java】JUC并发编程最新版通俗易懂_哔哩哔哩_bilibili 一、什么是JUC 从中就可以看出JUC&#xff0c;实质就是三个包&#xff0c;后面晖详细说明三个包下各个类功能。 java.…

程序员的测试课

git项目地址&#xff1a;GitHub - dreamhead/geektime-todo: Geektime Todo is a demo todo project for Geektime column. 1、实现一个Todo应用 设计规范 1、对于输入参数的检测&#xff0c;由入口部分代码进行处理。如空字符串。 2、Repository 的问题以运行时异常 的形式抛…

Django+Celery+Flower实现异步和定时任务及其监控告警

用Django框架进行web开发非常的快捷方便&#xff0c;但Django框架请求/响应是同步的。但我们在实际项目中经常会碰到一些耗时的不能立即返回请求结果任务如&#xff1a;数据爬取、发邮件等&#xff0c;如果常时间等待对用户体验不是很好&#xff0c;在这种情况下就需要实现异步…

SOFA Weekly|2023 我们一起加油、本周 Contributor QA

SOFA WEEKLY | 每周精选 筛选每周精华问答&#xff0c;同步开源进展欢迎留言互动&#xff5e;SOFAStack&#xff08;Scalable Open Financial Architecture Stack&#xff09;是蚂蚁集团自主研发的金融级云原生架构&#xff0c;包含了构建金融级云原生架构所需的各个组件&#…

RocketMQ 搭建

目录 1、什么是MQ&#xff1f;为什么要用MQ&#xff1f; 2、MQ的优缺点 3、几大MQ产品特点比较 4.RocketMQ在Windows的启动 1.下载RocketMQ 4.7.1版本 2.解压到本地磁盘并配置好JAVA_HOME和ROCKETMQ_HOME 3.修改runserver.cmd 4.启动server 5.修改runbroker.cmd 6.启动…

ROS2 基础概念 服务

ROS2 基础概念 服务1. Services2. 服务类型3. 查找服务4. 服务请求1. Services 服务基于 请求-应答 模型&#xff0c;而不是话题的 发布-订阅 模型 虽然话题允许节点订阅数据流并获得持续更新&#xff0c;但服务 仅在客户端专门调用时提供数据 还是启动海龟及其遥控节点为例&…

[标准库]STM32F103R8T6 点灯以及按键扫描

刚开始学32的时候&#xff0c;选择了基于HAL库进行开发&#xff0c;原因是HAL比较容易上手&#xff0c;像点灯、输出PWM、按键输入这种操作都很快捷。但是到ADCDMA这部分的时候发现&#xff0c;HAL库有一些地方我认为不是很合理和方便。比如DMA中断这部分&#xff0c;ST官方给出…

音视频开发系列--H264编解码总结

一、概述 H264&#xff0c;通常也被称之为H264/AVC&#xff08;或者H.264/MPEG-4 AVC或MPEG-4/H.264 AVC&#xff09; 对摄像头采集的每一帧视频需要进行编码&#xff0c;由于视频中存在空间和时间的冗余&#xff0c;需要用算法来去除这些冗余。H264是专门去除这些冗余的算法…

王者荣耀崩溃解决记录

王者荣耀竟然崩溃了 上周玩王者荣耀&#xff0c;突然就进不去了&#xff0c;点击开始游戏后应用直接就崩溃退出了。 第一反应&#xff0c;肯定是反馈给游戏客服。但是果然腾讯的游戏是找不到真客服的&#xff0c;全部都是机器人处理的&#xff0c;给了我一个毫无用处的官方回…

springboot中配置文件优先级以及分类,这你都可以不会吗?不会赶紧进来学( ̄(∞) ̄)

各位小伙伴大家好呀┗( ▔, ▔ )┛&#xff0c;马上过年了&#xff0c;但是感觉没啥期待的哈哈哈哈哈&#xff0c;现在的年说实话真的挺没劲的呜呜。 言归正传&#xff0c;我们大家在使用springboot时难免会写各种各样的配置信息&#xff0c;比如port&#xff0c;jdbc啊这些&am…

2022这一年:阳了、变轨和逆风

又到年末了&#xff0c;2022这一年应该会让人记忆深刻&#xff0c;于我而言这一年的感受有明显的分界线&#xff0c;在此之前的世界温暖一些&#xff0c;提供着能量&#xff0c;让人心生探索它的纷繁多彩&#xff1b;今年世界变得寒冷了&#xff0c;展示着它的严酷与无情。阳了…

再学C语言20:循环控制语句——for循环

在while循环中&#xff0c;建立一个重复执行固定次数的循环涉及到3个动作&#xff1a; 1&#xff09;初始化一个计数器 2&#xff09;计数器与某个有限的值比较 3&#xff09;每次执行循环&#xff0c;要在循环体中让计数器的值递增 其中&#xff0c;计数器的初始化在循环之…

【pandas】教程:6-如何计算摘要统计

Pandas 计算摘要统计 本节使用的数据为 data/titanic.csv&#xff0c;链接为 pandas案例和教程所使用的数据-机器学习文档类资源-CSDN文库 加载数据 import pandas as pdtitanic pd.read_csv("data/titanic.csv") titanic.head()PassengerId Survived Pclass \…

#Z0424. 树上的旅行

题目 Description 给出一棵有N个结点的树&#xff0c;给出Q个询问&#xff0c;求结点xj过结点K到节点yj的最短距离 Format Input 第一行一个数n 接下来共有n-1行&#xff0c;三个数u,v,len表示u和v之间存在一条边长为len 再给你Q&#xff0c;K。代表有Q个询问&#xff0…

视频 | bedtools使用介绍1

点击阅读原文跳转完整教案。基因组中的趣事&#xff08;二&#xff09;- 最长的基因2.7 million&#xff0c;最短的基因只有8 nt却能编码基因组中的趣事&#xff08;一&#xff09;&#xff1a;这个基因编码98种转录本1 Linux初探&#xff0c;打开新世界的大门1.1 Linux系统简介…