Thread的小补丁
Thread小补丁
- 线程状态
- New
- Runnable
- Waiting
- Timed_waiting
- Blocked
- 线程安全
- 线程的抢占式执行
- 同时对同一个变量进行修改
- 指令重排序
- 操作不是原子的
- 解决方案
- 万恶之源
- 优化我们自己的代码
- Synchronized和Volatile
上一篇博客中,我们简单介绍了线程Thread的一些知识,一些基本的使用,但是单单只是知道那么一点是远远不够的,这篇博客中我们将简单介绍线程的状态,以及线程中的重头戏:线程安全
线程状态
New
当我们创建好Thread类但是并没有start(),也就是说并没有真正创建好线程的时候,就会是这个状态,这也就是我们说的各就各位,预备的意思:
Thread thread = new Thread(()->{
});
System.out.println(thread.getState());
Runnable
Runnable就表示线程已经跑起来了或者说随时随地都可以上CPU,上战场的感觉:
Thread thread = new Thread(()->{
while (true){
System.out.println("hi,t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
System.out.println(thread.getState());
Waiting
Object object = new Object();
Thread t1 = new Thread(()->{
synchronized (object){
while (true){
System.out.println("hi,t1");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
Thread t2 = new Thread(()->{
synchronized (Demo7.class){
}
});
t2.start();
System.out.println(t1.getState());
Timed_waiting
Object object = new Object();
Thread t1 = new Thread(()->{
synchronized (object){
while (true){
System.out.println("hi,t1");
try {
Thread.sleep(100000);
// object.wait();
//wait
//1.释放锁
//2.阻塞
//3.等待唤醒拿到锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
Thread t2 = new Thread(()->{
synchronized (Demo7.class){
}
});
t2.start();
System.out.println(t1.getState());
Blocked
Object object = new Object();
Thread t1 = new Thread(()->{
synchronized (object){
while (true){
System.out.println("hi,t1");
try {
Thread.sleep(100000);
// object.wait();
//wait
//1.释放锁
//2.阻塞
//3.等待唤醒拿到锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
Thread t2 = new Thread(()->{
synchronized (object){
}
});
t2.start();
System.out.println(t1.getState());
System.out.println(t2.getState());
线程安全
线程安全问题是面壁笔试的常客,阿涛在这里尽量精简地给大家说说有关线程安全!
首先我们这里说的安全的意思是不尽如人意,并不是和危险相对应的那个安全的意思,有很多代码我们在单线程环境下能够正常使用,但是到了多线程就会漏洞百出,下面我们先来讲讲造成线程不安全的几个重要原因:
线程的抢占式执行
线程之间是抢占式执行的,大丈夫生于天地之间,岂能郁郁久居人下!大家出来混,都是线程,谁比谁高贵啊,凭什么你就比我先执行?线程第一篇博客的时候我就给大家讲过,一般情况下,main线程是会先执行的,但是极少数情况下,就会有那么一个跑的快的线程比mian线程快,俗称"水鬼"!
大家要记住线程的抢占式执行是诱发多线程不安全的"万恶之源"!
同时对同一个变量进行修改
注意我们这里的关键词:“同时”,“同一变量”,“修改”,换句话说当我们"不同时",“对不同变量”,“只读”,都是不会造成线程不安全的,其实也很好理解,两个线程都是修改的话,最终结果不论是判给谁,都不是我们所能够接受的!
指令重排序
指令重排序是编译器自动对我们的代码进行地智能优化,编译器会在不改变我们代码结果的情况下自动地对我们的代码进行一定的优化,一般在单线程情况下这里的优化做的是很好的,但是到了多线程,谁知道因为抢占式执行会出现什么幺蛾子呢?
就比如我们说的创建实例这个操作吧:创建实例,我们首先是会先申请一块内存空间,然后调用构造方法,最后会把创建好的实例对象放在内存里面去,但是在多线程情况下就有这样一种可能:我们还是先申请了一块空间,然后我们直接就把实例给了这个空间,最后我们才进行构造方法,请注意,我们这里的是多线程情况下,也就是说我们有一个线程本想拿到的是已经搭建好的实例对象,但是阴差阳错只是拿到了一个空空如也的对象,那么无论我们想要进行什么后续操作,那都是空中阁楼,都是纸上谈兵.
操作不是原子的
我个人感觉上面的对同一个变量进行修改是这个不是原子情况的一个具体的例子,这里我就不给兄弟们画图了,兄弟们自己脑补一下:就以自增操作为例,我们首先是要把内存中的值加载到寄存器上,然后对内存其上面的值进行自增,最后才是把内存器上面的值加载回内存中去,如果我们想要对同一个变量进行两次自增的话,有没有一种可能两次加载到寄存器的时间比较接近,于是与两个寄存器上面的值是同一个,那么我们这次自增操作就从最终的结果上看来也就是只自增了一次.
关键就是在于,自增操作分了三步走,如果我们现在有能力让这三部变成一步,那么是不是就可以有效解决这个问题呢?
解决方案
万恶之源
万恶之源是多线程编程自身与生俱来的问题,除非你有本事多线程底层逻辑优化优化,不然应该是只能选择接收适应它
优化我们自己的代码
针对同一个变量进行修改的问题,有的时候我们是可以通过优化自己写的代码,达到解决问题的效果的,就像之前我们在JDBC编程事务那块学习的,我们有时候可以让部分代码串行执行,诸如此类的方法并不罕见
Synchronized和Volatile
synchronized关键字可以解决我们说的很多的问题,加锁既可以让锁中的代码编程原子的,也可以解决内存可见性的问题:内存可见性就是当我们在一边读一边修改代码的时候,我们读取数据的速度是飞快的,如果我们一直在读取一个数据,并且每一次读取到的数据都是一样的,那么编译器就会大胆地帮我们进行一个优化,会把内存中的数据给加载到寄存器中,那么以后我们就不再会去内存中读数据了,就直接去寄存器中读数据了,从某种意义上来说这也是编译器的优化造成的,
而我们的Volatile关键字也是可以解决我们的内存可见性问题的,这就是手动关闭了我们的优化.
好了,今天关于线程的一些关键的知识到这里也就差不多了,希望我的博客能够帮助到大家!
百年大道,你我共勉!