一.线程的状态
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不光能保证原子性,还能保证内存可见性,同时还能禁止指令重排序