🌼什么是线程
线程属于进程之下,一个进程下可以有多个线程,一开始就存在的线程被称为主线程
主线程和其他线程之间地位是完全相等的,没有任何特殊性
线程是 OS(操作系统)进行调度(分配 CPU)的基本单位
🌼Java 线程在代码中的体现
🌷线程对象
Java 是一个面向对象的语言,线程在 Java 中也是以对象的形式体现的 —— java.lang.Thread 类(包括其子类)的一个对象
🌷在 Java 代码中创建线程
通过继承 Thread 类,并且重写 run 方法,实例化 Thread 对象,再进一步使用线程对象中的一些方法来操作对象
🌷启动线程
当手中有一个 Thread 对象时,调用其 start() 方法来启动线程
注意:
- 一个已经调用过 start() 的对象不能再调用 starrt() ,再调用就会有异常
- 调用 start() 方法时千万不能错调用成 run() 方法
🌷代码演示创建线程
public class MyThread extends Thread{ @Override public void run() { System.out.println("我是子线程"); } public static void main(String[] args) { MyThread m = new MyThread(); m.start(); System.out.println("我是主线程"); } }
🌼多线程下各个线程之间执行先后的随机性
start() 是在主线程中的语句,说明 start() 执行后,主线程正在 CPU 上,所以大概率是start() 的下一条语句先执行,但实际运行下,各个线程的执行先后都是不确定的
🌷什么情况下,子线程会被先执行
start() 执行完之后,非常碰巧的发生了一次线程调度,此刻主线程将不再持有 CPU,在调度之后,其他子线程持有 CPU 就会执行子线程中的语句
🌷什么情况下,会出现线程调度
- CPU 空闲
1.1 当前运行着的 CPU 执行结束了
1.2 外部条件将线程阻塞
1.3 线程主动放弃 CPU- 被调度器主动调度
2.1 高优先级线程抢占
2.2 时间片耗尽(这种情况最常见)
🌼线程安全
🌷线程之间的数据共享
- 线程之间共享的内存区域
堆区、方法区、运行时常量池区- 线程之间私有的区域
pc寄存器、栈
正是因为线程之间有数据共享,才会出现线程线程的不安全情况
🌷演示什么是线程不安全
public class Main { static int r = 0; static final int COUNT = 1_0000; static class Add extends Thread { @Override public void run() { for (int i = 0; i < COUNT; i++) { r++; } } } static class Sub extends Thread { @Override public void run() { for (int i = 0; i < COUNT; i++) { r--; } } } public static void main(String[] args) throws InterruptedException { Add add = new Add(); add.start(); Sub sub = new Sub(); sub.start(); add.join(); // 等待 add 线程执行结束 sub.join(); System.out.println(r); } }
这段代码本来的运行结果应该是 “ 0 ”,但是实际的运行结果却相差甚远,且每次的运行结果都是随机的
🌷线程不安全的原因
- 多个线程之间操作同一块数据(共享数据)
- 至少有一个线程在修改这块共享内存
由于线程调度的原因,一个线程在刚读取到共享内存且还没修改时可能被 CPU 调度,然后另一个线程再读取这块内存进行修改,修改完之后,上一个线程再回到原来的状态,但因为之前的状态共享内存的值还没被修改,就会导致同一个值被多次操作,从而导致最终的值出现巨大偏差
🌷原子性
程序员的预期是 r++ 或者 r-- 是一个原子性的操作(全部完成或全没完成),但实际执行起来,保证不了原子性,所以会出错
原子性被破坏是线程不安全最常见的原因
🌷系统角度分析线程不安全的原因
内存可见性
一些线程多数据的操作,很可能其他线程是无法感知的,甚至某些情况下,会被优化到完全看不到的结果,当有共享内存存在时,就会导致同一个值被多次操作
代码重排序
我们写的程序,往往是经过中间很多环节优化的结果,并不保证最终执行的语句和我们写的语句是一模一样的
所谓的重排序,就是指:执行的指令和书写的指令不一致