3.5多线程

一.线程的状态

1.NEW

安排了工作,还未开始行动

把Thread对象创建好了,但是还没有调用start

java内部搞出来的状态,与PCB的状态没什么关系

2.TERMINATED

工作完成了

操作系统的线程执行完毕,销毁了,但是Thread对象还在,获取的对象

3.RUNNABLE

可以工作的,又可以分为正在工作中和即将开始工作

也就是就绪状态

处在这个状态的线程,就是在就绪队列中

随时可以被调度到CPU上

如果代码中没有进行sleep.也没有其他的可能导致阻塞的操作,代码大概率是处在runnable操作

4.TIMED_WAITING

这几个表示排队等着其他事情

代码中调用了sleep,就会进入到TIMED_WAITING

join(超时时间)

意思就是当前线程在一定时间之内是阻塞状态

到了一定时间.阻塞状态解除的意思

5.BLOCKED

这几个都表示排队等着做其他事情

当前线程在等待锁,导致了阻塞- -----------synchroized

6.WAITING:

表示排队等着其他事情

当前线程等待唤醒,导致了阻塞---------------------wait

7.线程状态转换

二.线程安全问题

操作系统调度线程的时候,是随机的(抢占式执行)

所以可能导致程序的执行出现一些bug

如果是因为这样的调度随机性引入了bug.那么就认为线程是不安全的

1.线程不安全典型案例

为什么结果不是10_0000呢

因为可能是t1和t2同时增加一个变量的时候就会值加一次

2.CPU的原理

count++

站在cpu的角度来看,其实是三个指令

1.把内存里额度count的值.放到CPU寄存器里-----load

2.把寄存器中的值给+1

3.把寄存器的值写回内存的count中

正是因为前面说的"抢占式执行,这就导致了两个线程同时执行这三个指令的时候,顺序充满了随机性

3.解决线程安全问题-上锁

在自增之前,先加锁--lock

自增之后,再解锁 ---unlock

t1把这把锁占用,此时t2尝试lock就会阻塞

lock会一直阻塞,直到t1线程执行了unlock

通过这里的阻塞就把乱序额并发变成了一个串行操作

4.加锁的方式

1) synchronized

表示进入方法,就会自动加锁,离开方法就会自动解锁

当一个线程加锁成功的时候,其他线程尝试加锁,就会触发阻塞等待,

此时对应的线程就处在BLOCKED状态

这个阻塞会一直持续到,占用锁的线程把锁释放

5.线程不安全原因

1.线程是抢占式执行.线程之间的调度充满随机性

2.多个线程对同一变量进行修改操作

3.针对变量的操作不是原子的

(可以类比数据库的事务)

针对有些操作,比如读变量的值,只是对应一条机器指令,这样的操作本身可以视为原子性

通过加锁操作,也就把好几个指令打包成一个原子的

4.内存可见性

举例:针对同一个遍历.一个线程进行读操作,(循环很多次)

一个线程进行修改操作(合适的时候执行一次)

这就是java编译器产生的代码优化产生的效果

6.预防内存可见性

始终无法判断isquit已经变了,因为这里编译器已经悄悄优化了

1.使用synchronized关键字

synchronized不光能保证指令的原子性,同时也保证了内存的可见性

被synchronized包裹起来的代码,编译器就不敢轻易优化

2.使用volatile关键字

volatile与原子性无关,但是能够保证内存可见性,禁止编译器做出上述优化

7.指令重排序

也是编译器优化的操作

编译器会能调整代码的前后顺序

保证逻辑不变的情况下,调整,提高程序执行的顺序

可以用synchronized不光能保证原子性,还能保证内存可见性,同时还能禁止指令重排序

猜你喜欢

转载自blog.csdn.net/m0_72618437/article/details/129353236