进程和线程
进程线程两者之间的对比
线程的上下文切换 并行和并发
并行 parallel并发 concurrent举例说明并行 并发的测试结果 同步 异步关于日志文件的配置
pomlogback.xml 创建以及运行线程
Thread 与 Runnable 之间的关系FutureTask 线程运行原理
栈与栈帧图解栈与栈帧栈帧小结 线程的上下文切换线程中的常用方法
start 与 runsleep 与 yieldyield 与 sleep 之间的区别join() 线程的同步可以使用同步 小案例join(long) 有时间限制的等待interrupt() 打断 sleep wait join 的线程 阻塞线程interrupt 打断正常运行的线程interrupt 的多线程设计模式 两阶段终止模式 (Two Phase Termination)
监控线程的案例 interrupt 打断 park 线程不推荐使用的方法 过时的方法 线程优先级
小结 案例 防止 CPU 占用率达到 100% 主线程以及守护线程线程的运行状态 5 种? 6 种?
6 种状态的代码演示 阶段小结共享问题
共享资源带来的问题 synchronized线程安全分析wait / notify线程状态切换活跃性Lock
进程和线程 进程 线程 两者之间的对比 线程的上下文切换当系统的内存不够的时候,可以关闭一些线程,将内存由其他的线程进行使用,这个时候需要进行线程的切换,存在进程上下文的概念;
并行和并发 并行 parallel多核 cpu 同时执行多个线程,是真正的并发;
并发 concurrent单核 cpu 进行线程的快速切换
操作系统中存在任务调度器,将 cpu 时间片交给不同的线程执行,cpu 在线程之间切换的速度非常快,人是感觉不到的;
微观串行,宏观并行;
举例说明 并行 并发的测试结果 同步 异步从方法调用的角度来讲
需要等待结果的返回,才能继续运行的是同步
不需要等到结果的返回,就能继续执行操作是异步
同步在多线程的另外含义:多个线程之间同步进行
关于日志文件的配置 pom下面的两个配置都是需要的
logback.xmlorg.projectlombok lombok 1.18.10 ch.qos.logback logback-classic 1.2.3
创建以及运行线程%date{HH:mm:ss.SSS} %c [%t] - %m%n
线程的创建以及启动分为两步,先创建,然后运行
thread
@Slf4j(topic = "c.test")
public class Test1 {
public static void main(String[] args) {
Thread t = new Thread() {
@Override
public void run() {
log.debug("running");
}
};
t.setName("t1");
t.start();
log.debug("main is running");
}
}
Runnable
@Slf4j(topic = "c.Test2")
public class Test2 {
public static void main(String[] args) {
Runnable r = () -> {log.debug("running");};
Thread t = new Thread(r);
t.setName("t2");
t.start();
log.debug("main is running");
}
}
Thread 与 Runnable 之间的关系
@Slf4j(topic = "c.Test12")
public class Test12 {
public static void main(String[] args) throws InterruptedException {
// 实际上使用的是创建的 Thread 线程的构造器 (Runnable,"name")
// 由于里面没有输入的参数,直接使用 lambda 表达式即可
// 任何对象在创建的时候,都是会使用构造函数的,构造函数使用什么的形式,将相关的参数进行传递即可
Thread t1 = new Thread(() -> {
System.out.println("this a new thread");
}
}, "t1");
t1.start();
}
}
FutureTask
接收 Callable 类型的参数,用来处理返回结果的情况
线程之间的通讯可以用的到
@Slf4j(topic = "c.Test3")
public class Test3 {
public static void main(String[] args) throws Exception {
FutureTask task = new FutureTask(new Callable() {
@Override
public Integer call() throws Exception {
log.debug("running...");
return 100;
}
});
Thread t = new Thread(task, "t1");
t.start();
// 获取返回结果 主线程得到的返回结果
log.debug("{}", task.get());
log.debug("main is running...");
}
}
线程运行原理
栈与栈帧
线程启动的时候,会分配一个虚拟机栈,栈帧就是调用的每一个方法,在方法调用结束之后,方法会退出来栈,释放内存;
图解栈与栈帧public class TestFrams {
public static void main(String[] args) {
method1(10);
}
private static void method1(int x) {
int y = x + 1;
Object m = method2();
System.out.println(m);
}
private static Object method2() {
Object n = new Object();
return n;
}
}
方法调用结束,将 method1 以及 method2 内存释放即可
线程是有自己的栈帧,自己的程序计数器,相互之间是不会影响的;
线程的上下文切换什么是线程的上下文切换
线程从使用 cpu 到不使用 cpu 之间进行的一次转换,转换到了另外的线程进行执行
对于线程执行的状态是需要记录的,否则,不知道从什么地方开始执行线程;
线程中的常用方法join 用于线程之间的通信
start 与 runstart 表示启动线程
run 表示线程启动之后需要执行的代码
启动一个线程必须使用 start() ,直接调用 run 方法,不能启动多线程的执行,只是一个普通的方法执行了;
sleep 与 yield
yield:运行的状态到达就绪的状态,将 cpu 时间片交出来,让其他的线程进行使用;
yield 的实现依赖于任务调度器;有时候可能出现的结果是,想要将 cpu 时间片让出去,但是,没有其他的线程需要使用,所以出现让不出去的结果;
一个线程调用了 yield 还是有机会继续获得 cpu 时间片继续运行的;
但是 Time Waiting 状态就是 阻塞状态,必须等到时间到了以后,才能执行,在这个期间是没有办法得到 cpu 时间片的;
sleep 是存在等待时间的,但是 yield 是几乎没有等待时间的;
join() 线程的同步可以使用@Slf4j(topic = "c.Test10")
public class Test10 {
static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
sleep(1);
log.debug("结束");
r = 10;
},"t1");
t1.start();
// 主线程执行到这里的时候,不会继续执行下去,而是会等待 t1 线程执行结束之后,才会继续执行自己的代码
t1.join();
log.debug("结果为:{}", r);
log.debug("结束");
}
}
主线程就是想要获取到 t1 线程中的数值,怎么比较好的获取到呢?
此时使用 join() 方法即可;
直接使用 sleep() 这个解决方式不是十分的好;
join() 等待线程结束;在上面的代码的体现就是主线程会一直等待知道 t1 线程执行结束;
同步 小案例上面的案例中,体现出来了同步的概念,在调用了之后,需要等待运行结果,这就是同步;
如何实现多个线程的同步呢?可以使用分别调用多个线程的 join() 方法即可;
多线程运行的时间计算:需要的最长时间的线程的时间就是总运行的时间;
join(long) 有时间限制的等待当等待的时间超过了限制之后,就不会继续等待了;直接退出去不等待了;
具有时效的等待;
interrupt() 打断 sleep wait join 的线程 阻塞线程
在打断标志的地方,按道理打断标志应该为 true ;但是这种阻塞是的线程在打断之后,报出来了异常,打断为假
结合打断标识,可以主线程可以让分支线程主动的选择是不是确定自己结束后面的代码执行;
@Slf4j(topic = "c.Test12")
public class Test12 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
while (true) {
// 主线程中虽然让 t1 线程进行打断,但是具体是不是打断需要 t1 线程自己决定,主线程打断之后
// 打断标识在这里会变成真的,所以可以使用打断标识进行正确的打断即可
boolean interrupt = Thread.currentThread().isInterrupted();
if (interrupt) {
log.debug("被打断了,退出去循环");
break;
}
}
},"t1");
t1.start();
// 为什么需要主线程先 sleep() 一秒钟呢?为了让 t1 线程先运行一下,不然 t1 还没有sleep 就打断了 程序没有达到自己的期望输出
Thread.sleep(1);
log.debug("interrupt");
// 这个时候使用了打断标识,可以合理的将线程进行打断处理
t1.interrupt();
}
}
interrupt 的多线程设计模式 两阶段终止模式 (Two Phase Termination)
简单理解就是一种好的使用一个线程停止另外一个线程的方法;
这个好的方法就是使用 两阶段终止莫斯
上面的错误思路是,stop 以及 exit 导致程序错误的执行,是不建议使用的;
每隔一段时间进行线程的监测,观察是否打断线程;
后面的存在异常之后,打断标记会变为 false 此时可以重新对于 false 进行处理将其改变为 true 或者不进行改变;当程序运行到打断标记为 true 的时候,线程才会被终止;
@Slf4j(topic = "c.Test13")
public class Test13 {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination twoPhaseTermination = new TwoPhaseTermination();
twoPhaseTermination.start();
Thread.sleep(3500);
twoPhaseTermination.stop();
}
}
@Slf4j(topic = "c.Two")
class TwoPhaseTermination{
private Thread monitor;
// 启动监控线程
public void start() {
monitor = new Thread(()->{
while (true) {
Thread current = Thread.currentThread();
if (current.isInterrupted()) {
log.debug("终止当前线程的执行");
break;
}
try {
Thread.sleep(1000);
log.debug("执行监控记录");
} catch (InterruptedException e) {
e.printStackTrace();
// 重新设置打断标志,因为是在异常中执行的,打断标志为 false,重新设置之后,为了打断标志为 真
// 打断标志会变为 true 线程才会被真正的终止
current.interrupt(); // 两次打断 终止线程的执行
}
}
});
monitor.start();
}
// 关闭监控线程
public void stop() {
// monitor 就是监测线程,这个是正常调用线程的 interrupt 方法,其他的方法也是可以调用的;
monitor.interrupt();
}
}
运行结果:
19:58:05.922 c.Two [Thread-0] - 执行监控记录 19:58:06.930 c.Two [Thread-0] - 执行监控记录 19:58:07.932 c.Two [Thread-0] - 执行监控记录 java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at com.luobin.concurrent.TwoPhaseTermination.lambda$start$0(Test13.java:38) at java.lang.Thread.run(Thread.java:748) 19:58:08.421 c.Two [Thread-0] - 终止当前线程的执行interrupt 打断 park 线程
@Slf4j(topic = "c.Test15")
public class Test15 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
log.debug("parking");
LockSupport.park();
log.debug("unpark");
log.debug("打断状态:{}",Thread.interrupted());
// 上面的 interrupted 修改为 false 下面的 park 会继续的执行
// park 会继续执行
LockSupport.park();
log.debug("unpark");
},"t1");
t1.start();
// 放置还没有进入 park 直接打断
Thread.sleep(1000);
// 打断 park 的状态
t1.interrupt();
}
}
小结:
park 方法本身会使得当前线程进入阻塞状态;
Thread.currentThread().isInterrupted() 会打断 park 线程;使得线程打断,停止运行;
Thread.interrupted() 不会打断 park park 会继续的执行;不推荐使用的方法 过时的方法
容易导致线程死锁的产生,不建议使用;过时的方法
可以设置优先级别,最低的优先级别是 1 ,最高的优先级别是 10,默认的优先级别是 5 ;
线程的优先级,会提示调度器调度该线程,仅仅是一个提示,调度器是可以忽略你的提示的;
在 cpu 比较忙的时候,线程的优先级高的线程会获得更多的时间片,但是在 cpu 比较闲的时候,优先级几乎是没有作用的;
@Slf4j(topic = "c.Test9")
public class Test9 {
public static void main(String[] args) {
Runnable task1 = () -> {
int count = 0;
for (;;) {
System.out.println("---->1 " + count++);
}
};
Runnable task2 = () -> {
int count = 0;
for (;;) {
// Thread.yield();
System.out.println(" ---->2 " + count++);
}
};
Thread t1 = new Thread(task1, "t1");
Thread t2 = new Thread(task2, "t2");
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
}
}
小结
无论是 yield 还是设置的优先等级,都不能最终的决定任务调度的最终结果,只有任务调度器本省可以决定;
yield 以及 线程优先级仅仅是对于线程调度器的一个提示而已;
案例 防止 CPU 占用率达到 100%
只是编写 while true 存在问题中间可以加一个 sleep() 避免空转;
在默认的情况下面,Java 进行需要等待所有的线程执行完毕之后,才会结束进行的运行,但是不会等待守护线程的结束,守护线程没有执行结束,也是会被强制结束的,备胎舔狗线程;
线程的运行状态 5 种? 6 种?操作系统的层面
从 Java API 层面进行描述
Java 源代码中将线程的运行状态分为 6 种,使用枚举记性分类的;
NEW 表示线程刚创建,没有使用 start 方法 RUNNABLE 当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行) BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分 TERMINATED 当线程代码运行结束6 种状态的代码演示
@Slf4j(topic = "c.TestState")
public class TestState {
public static void main(String[] args) throws IOException {
// new
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("running...");
}
};
// runnable
Thread t2 = new Thread("t2") {
@Override
public void run() {
while(true) { // runnable
}
}
};
t2.start();
// Terminate
Thread t3 = new Thread("t3") {
@Override
public void run() {
log.debug("running...");
}
};
t3.start();
// 有时间限制的等待
Thread t4 = new Thread("t4") {
@Override
public void run() {
synchronized (TestState.class) {
try {
Thread.sleep(1000000); // timed_waiting
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t4.start();
// 需要等待 线程 t2 的执行结束才会停止执行代码。所以处于 waiting z状态
Thread t5 = new Thread("t5") {
@Override
public void run() {
try {
t2.join(); // waiting
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t5.start();
// TestState.class 先被 t4 线程加锁,然后下面的这个线程拿不到锁,所以到了 blocked(被封锁) 的状态
Thread t6 = new Thread("t6") {
@Override
public void run() {
synchronized (TestState.class) { // blocked
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t6.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
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());
System.in.read();
}
}
执行结果 11:59:52.996 c.TestState [t3] - running... 11:59:53.501 c.TestState [main] - t1 state NEW 11:59:53.502 c.TestState [main] - t2 state RUNNABLE 11:59:53.502 c.TestState [main] - t3 state TERMINATED 11:59:53.502 c.TestState [main] - t4 state TIMED_WAITING 11:59:53.502 c.TestState [main] - t5 state WAITING 11:59:53.502 c.TestState [main] - t6 state BLOCKED阶段小结 共享问题 共享资源带来的问题 synchronized 线程安全分析 wait / notify 线程状态切换 活跃性 Lock



