在操作系统中的线程,自身是有一个状态的~,但是Java Thread是对系统线程的封装,把这里的状态又进一步精细化了。
1.NEW 系统中的线程还没创建出来呢!只是有一个Thread对象~
public class Main1 { public static void main(String[] args) { Thread t=new Thread(()->{ System.out.println("hello"); }); //在启动之前,获取线程状态----》new System.out.println(t.getState());//NEW t.start(); } }
该段代码的运行结果为:
2.TERMINATED:系统中的线程已经结束了,Thread对象还在~
public class Main1 { public static void main(String[] args) throws InterruptedException { Thread t=new Thread(()->{ System.out.println("hello"); }); //在启动之前,获取线程状态----》new System.out.println(t.getState());//NEW t.start(); Thread.sleep(1000); System.out.println(t.getState());//TERMINATED } }
该段代码的运行结果为:
3.RUNNABLE就绪状态:
- 正在CPU上运行
- 准备好随时可以去CPU运行
跟上述的代码差不多,只不过用了while()循环
public class Main1 { public static void main(String[] args) throws InterruptedException { Thread t=new Thread(()->{ while (true){ System.out.println("hello"); } }); //在启动之前,获取线程状态----》new System.out.println(t.getState());//NEW t.start(); Thread.sleep(1000); System.out.println(t.getState());//TERMINATED } }
该段代码的运行结果为:
4.TIMED_WAITING指定时间等待:如sleep()方法
public class Main1 { public static void main(String[] args) throws InterruptedException { Thread t=new Thread(()->{ while (true){ System.out.println("hello"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); //在启动之前,获取线程状态----》new System.out.println(t.getState());//NEW t.start(); Thread.sleep(1000); System.out.println(t.getState());//TERMINATED } }
5.BLOCKED:表示等待锁将出现的状态
6.WAITING:使用wait()方法出现的状态
一条主线,三个支线
理解线程的状态:意义就算让我们能够更好的进行多线程代码的调试~
多线程带来的风险——》线程安全?(重点难点考点)
多线程不安全是指:某个代码再多线程环境下执行,会出bug!
本质上是因为:多线程的各线程的调度顺序是不确定的~
我们来看一下下述代码:两个线程同时对一个变量各自自增5W次(线程不安全)
class Counter{ private int count=0; public void add(){ count++; } public int get(){ return count; } } public class Main2 { public static void main(String[] args) throws InterruptedException{ Counter counter=new Counter(); //搞两个线程,两个线程分别对这个count自增5W次 //线程1 Thread t1=new Thread(()->{ for (int i = 0; i < 50000; i++) { counter.add(); } }); //线程2 Thread t2=new Thread(()->{ for (int i = 0; i < 50000; i++) { counter.add(); } }); //启动线程 t1.start(); t2.start(); //等待两个线程执行结束,然后看一下结果 t1.join(); t2.join(); System.out.println(counter.get()); //预期结果是10W,但是,实际结果像是一个随机值,每次执行的结果都不一样 } }
在上述代码中,两个线程分别对count自增5W次,预期结果是10W,但是,实际结果像是一个随机值,每次执行的结果都不一样,下面的运行结果是笔者截自五次的运行,那么,我们来看一下:
经过上述的运行结果,我们可以得出:实际结果与预期结果不一样:就是Bug,这就是由多线程引起的Bug——》线程不安全/线程安全问题。
那么,我们来思考一下:为啥会出现count随机值的问题呢??
主要是和:线程相关的调度随机性密切相关!
上述代码中:count++操作,本质上是三个CPU指令构成的~:
- load把内存中的数据读取到CPU寄存器中
- add就算把寄存器中的值进行+1运算
- save把寄存器中的值写回到内存中
说白了,归根结底,线程安全问题:全是因为线程的无序调度导致了执行顺序的不确定,从而结果就发生变化了!(罪魁祸首,万恶之源)
那么,线程不安全的原因又有哪些呢??
- 抢占式执行(罪魁祸首,万恶之源)
- 多线程修改同一个变量:一个线程修改同一个变量——》安全;多个线程读取同一个变量——》安全;多个线程修改不同变量——》安全
- 修改操作:不是原子的(原子:不可分割的最小单位称为原子)。如上述的count++操作,就不是原子的,里面可以分为三个操作:load,add,save,因此,某个操作对应单个CPU指令就是原子的,如果这个操作对应多个CPU指令,大概率就不是原子的!正是因为不少原子的,导致两个线程的指令排列存在更多变数了!(如果直接使用=(等号)进行赋值,就是一个原子的操作(count=4)
- 内存可见性引起的线程不安全,另外的场景会引起这样的问题,和当前count++列子无关
- 指令重排序引起的线程不安全,另外的场景会引起这样的问题,和当前count++列子无关
如何解决线程不安全问题呢??这就得从原因入手了~~
如果用join()来防止线程的抢占式执行,那么,还要多线程干啥??直接一个线程串行执行呗!(多线程的初心:进行并发编程,更好的利用多核CPU)
思考:能否让count++变成原子的呢??当然有办法——》加锁!!
锁的核心操作有两个,1.加锁,2.解锁
一旦某个线程加锁了之后,其他线程也想加锁,就不能直接加上,就需要阻塞等待,一直等到拿到锁的线程释放了锁为止!!
记住:线程调度的方式是:抢占式执行!!
由于抢占式执行,导致了线程之间的调度是随机的!!当1号滑稽释放锁之后,等待锁的2号滑稽和3号滑稽谁能抢先一步拿到锁,成功加锁是不确定的!~
本文关于:线程的状态 and 线程安全大致到此结束,若是对Java中如何进行加锁的,感兴趣,那么,请参考下篇文章~~