网站建设 模版选择中心,医院网站asp,wordpress生成静态地图,佛山仿站定制模板建站上一个篇章浅浅了解了一下 线程的概念#xff0c;进程与线程的区别#xff0c;如何实现多线程编程。
而且上一章提到一个重要的面试点#xff1a; start 方法和 run 方法的区别。 start 方法是从系统那里创建一个新的线程#xff0c;这个线程会自动调用内部的run 方法进程与线程的区别如何实现多线程编程。
而且上一章提到一个重要的面试点 start 方法和 run 方法的区别。 start 方法是从系统那里创建一个新的线程这个线程会自动调用内部的run 方法 run 方法是一个线程的执行入口所有的实现逻辑都写在该方法里。 从概念上来说这两个方法是属于线程的属性。我们接着这个线程属性继续来认识线程。
线程属性
就像我们之前写的代码 我们如何让某个线程中断呢?
这里的中断意味着让线程停下来线程的终止。
本质上来说让线程终止的办法只有一种让线程的入口方法 执行完毕。无论是是 return 、抛出异常还是别的方法都是执行完毕。
这里就需要提到 给线程设定一个结束标志。
给线程设定一个结束标志
来看下面的这段代码 我们可以选择这种方式来控制循环不让他写的那么死。 但这仍是我们自己设置的变量而不属于线程的属性线程中也有个方法interru方法
interru方法
我们来看看这一段代码 我们这里就用到了几个线程的属性
Thread.currentThread()、isInterrupted()、t.interrupt()
上述的e.printStackTrace 就是打印出异常信息。
我们来看看结果 我们发现在main线程睡眠了 3s 之后并没有打印完异常就结束而是继续线程 t 的运行。
这是为什么呢 这就需要来聊聊 interrupt 方法的作用了
将标识符设置为 true 如果该线程正在阻塞状态中例如sleep后面还有其他方法也会使线程阻塞会直接被interrupt 方法唤醒此时通过抛出异常就会让 sleep 立即结束。
按照 interrupt 方法的作用此时应该结束了啊t 线程这么还会继续呢
这里有个非常重要的点; 当 sleep 被唤醒以后会自动将 isInterrupted 设置为 false也就是说将异常标志位清空了true - false
sleep 为什么要清空异常标志位呢
目的是为了让这个线程能够对于线程什么时候结束有一个更精准的控制。
当前的 interrpt 方法是告诉线程你该结束了至于什么时候结束都是由代码灵活控制的。
interrpt 方法 是个通知而不是命令。
等待线程 -- join 方法
线程与线程之间的的并发执行的线程间的调度是无序的我们无法判断两个线程之间谁先结束谁后结束。
例如 但是我们可以通过 join 方法进行控制例如 在t 执行 join 方法时如果 t 线程尚未结束那么main线程就会进入 Blocking 阻塞状态因为t.join 在 main线程中执行。
main 线程代码走到这一行 就不参与 cpu 的执行调度了。
如果这个有多个线程并发执行 那么 只是main 线程不参与线程的调度其余的线程任然是抢占执行的。
我们使用的 join 是无参的版本我们还存在另一个带参的版本。
这个带参的版本参数作为等待的最大时间超过了这个最大时间t 线程还没有结束那么main 线程 就会被唤醒不再是Blocking 状态 就会继续执行。
而上面不带参的版本就是死等等不到 t 线程结束mian 线程就不会开始。
既然上面都提到了线程状态那么就简单理解一下线程状态。
线程状态
Java中线程的状态分为6种
1. 初始(NEW)新创建了一个线程对象但还没有调用start()方法。 2. 运行(RUNNABLE)Java线程中将就绪ready和运行中running两种状态笼统的称为“运行”。 线程对象创建后其他线程(比如main线程调用了该对象的start()方法。该状态的线程位于可运行线程池中等待被线程调度选中获取CPU的使用权此时处于就绪状态ready。就绪状态的线程在获得CPU时间片后变为运行中状态running。 3. 阻塞(BLOCKED)表示线程阻塞于锁。 4. 等待(WAITING)进入该状态的线程需要等待其他线程做出一些特定动作通知或中断。 5. 超时等待(TIMED_WAITING)该状态不同于WAITING它可以在指定的时间后自行返回。 6. 终止(TERMINATED)表示该线程已经执行完毕。 我们简单理解就是一条主线三条支线。
我们就简单了解一下就好后面还会再聊到。
简单了解线程安全
对于多线程编程雀氏提供了很多便利我们可以再同一时间完成多个任务相比于多进程编程多线程还降低了 cpu 资源的占用。
这几章都提到了多线程之间的线程调度是抢占式执行的所以在此基础之上会产生很多的线程安全问题。
本章就稍稍介绍认识一下线程安全的问题就行。
本章以一个不安全的例子来认识
例如我们设置一个 count 两个线程各执行 5 w 次自增运算。
代码如下
class Count {private int count 0;public void add() {count;}public int get() {return count;}
}
public class demo10 {public static void main(String[] args) throws InterruptedException {Count count new Count();Thread t1 new Thread(() - {for (int i 0; i 50000; i) {count.add();}});Thread t2 new Thread(() - {for (int i 0; i 50000; i) {count.add();}});t1.start();t2.start();t1.join();t2.join();System.out.println(count.get());}
}运行一次看看结果 再来试试看 再来一次 从结果上来看它是一个随机数。
至于为什么会产生一个随机数我们需要了解在这执行的内部发生了什么事情
对于这个 count 这个操作本质上是由 cpu 上三个指令构成的
load 从内存中将数据读取到 cpu 寄存器中add 在 cpu 中的值进行 1 运算save 把寄存器中的值写入到内存中
由于是抢占式执行的那么就会产生如下等问题 这个 3 的执行顺序就变成不确定的了产生多种排列组合结果。
只有 25 亿分之 1 的可能得到 10w 这个数字也有可能会产生结果小于 5w 具体的可以自己排列组合看看。
对于 t1 和 t2 这两个线程可能式运行在同一个 cpu 核心上执行也有可能在不同的 cpu 核心上执行。
归根结底线程安全问题全是因为线程之间的无序调度导致了执行顺序不确定结果不确定。