多线程
是一个独立的执行流,解决并发编程这样的问题(虽然进程也能行,但是不如线程更轻量)
上面说的“就绪”和“阻塞”都是针对系统层面上的线程的状态(PCB)
在java中Thread类,对于线程的状态又进一步细化了
- NEW :安排了工作,还未开始行动
把Thread对象创建好了,但是还没有调用start
- TERMINATED:工作完成了
操作系统的线程已经执行完毕,销毁了但是Thread对象还在
以上两个状态,是java内部搞出来的状态。就和操作系统PCB没有任何关系
- RUNNABLE:可工作的,又可分为正在工作和即将开始工作
就绪状态,处在这个状态的线程,就是在就绪队列中,随时可以调用到PCB上,如果代码中没有sleep,也没有其他的可能导致阻塞的操作,代码大概率处在Runable状态
- TIMED-WAITING:这几个都表示排队等其他事情
代码中调用了sleep就进入到这个状态,join(超时时间)意思就是当前这个线程在一定时间之内,是阻塞状态
- BLOCKED:这几个都表示排队等其他事情
当前这个线程在等待锁,导致了阻塞(阻塞状态之一)synchronized
- WAITING:当前这个线程在等待唤醒,导致了阻塞(阻塞状态之一)
为啥要这么细分?开发过程中经常会遇到一种情况,程序“卡死”,一些关键的线程阻塞了在分析卡死原因的时候,第一步就可以先来看看当前程序里的各种关键线程所处的状态
线程状态转换简图:
线程安全问题
整个多线程中最重要,也是最复杂的问题,如果面试官问了线程相关的内容,就一定会问到线程安全,日常开发者中如果用到多线程编程,也一定会涉及到线程安全问题
为什么会有这样的问题?
操作系统在调度线程的时候是随机的(抢占式执行)正是因为这些随机性,可能导致程序的执行出现了一些Bug,如果认为这样的调度随机性引入了bug就认为代码是线程不安全的,如果是因为这样调度的随机性,也没有带来bug,就认为代码是线程安全的(指的是没有bug)
一个线程不安全的典型案例:使用两个线程对同一个整型变量进行自增操作,每个线程自增5w次看最后的结果
class Count {
public int count = 1;
public void increase() {
this.count ++;
}
}
public class Main {
private static Count counter = new Count();
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for(int i = 0; i < 50000; i ++) {
counter.increase();
}
});
Thread thread1 = new Thread(() -> {
for(int x = 0; x < 50000; x ++) {
counter.increase();
}
});
thread.start();
thread1.start();
System.out.println(counter.count);
}
}
count ++ 到底干了什么,站在CPU的角度是三个指令
正因为“抢占式执行”,这就导致了两个线程同时执行这三个指令的时候,顺序上充满了随机性
多线程,线程安全问题,如何解决上述问题,加锁!
java中加锁的方式有很多,最常用的就是
synchronized
,这样的关键字(当一个线程加锁成功的时候,其他线程就会尝试加锁,就会触发阻塞等待,此时对应的线程就会处在BLOCKED状态),阻塞会一直持续到,占用锁的线程把锁释放为止
啥样的代码会产生这样线程不安全的问题呢?
不是所有的线程都要加锁(如果这样了,多线程的并发能力就形同虚设)
产生线程不安全的原因:
1. 线程是抢占式执行,线程之间的调度充满随机性,【线程不安全的万恶之源】
2. 多个线程对同一个变量进行操作的时候(如果多个线程针对不同的变量进行修改没事,如果多个线程针对同一个线程进行读,也没事)
3. 针对变量的操作不是原子的,针对有些操作,比如:读变量的值,只是一条机器指令,此时这样的操作本身就可以视为原子,通过加锁操作也就把好几个指令打包成一个原子的
4. 内存可见性,也会影响到线程安全。一个具体的例子:针对同一个变量,一个线程进行读操作(循环进行很多次)一个线程进行修改操作(合适的时候执行一次)
5. 指令重排序,也会影响到线程安全问题
synchronized的使用方法:
- 直接修饰普通的方法。(当两个线程同时尝试对同一个对象进行加锁的时候,才有竞争如果是两个线程在针对不同对象加锁,就没有竞争
- 修饰一个代码块。需要显示指定针对那个对象加锁(java中的任意对象都可以作为锁对象
- 修饰一个正常方法。相当于针对当前类对象加锁
加锁,就没有竞争
2. 修饰一个代码块。需要显示指定针对那个对象加锁(java中的任意对象都可以作为锁对象
3. 修饰一个正常方法。相当于针对当前类对象加锁
[外链图片转存中…(img-H7iJ26pb-1721024434413)]