目录
synchronized的4种加锁(synchronized加锁就是修改对象头)
2.内存可见性问题,指令重排序问题--->一个线程读一个线程写
什么是线程不安全? ->在随机调度之下,线程执行有多种可能,其中某些可能会导致代码出bug就称为线程不安全.
1.造成线程不安全的原因
1.操作系统的随机调度/抢占式执行(万恶之源)-->无法改变
2.多个线程修改同一个变量(一个字都不能少)--->尽量避免
3.有些修改操作,不是原子的!(不可拆分的最小单位,就叫原子 即对应一条机器指令)--->通过加锁操作,把指令打包
4.内存可见性问题(内存改了,但是在优化的背景下,读不到,看不见)
如:线程1一直在读取硬盘上的资源再判断,在多次读取硬盘并获得相同的结果后编译器会认为这样的做法过于低效,然后就会省略读取的过程,一直判断,若此时有线程2需要这个判断结果(读取到数据后的判断结果)就会出现问题.
5.指令重排序(也是编译器,操作系统等的优化,调整了代码的执行顺序)
最常见的就是对象new的问题
分为三步:1.创建内存空间
2.往内存空间上构造对象
3.把这个内存的引用赋值给你要创建的变量
有时候编译器会认为先执行3或者先执行2最高效,就会出现指令顺序倒转的问题,这时另一个线程尝试读取变量的引用就会出现问题
2.线程不安全的解决方法
1.对一些非原子的代码加锁--->两个线程写
synchronized(同步) 加锁
(在IO场景中,同步指调用者自己负责获取到调用结果;异步指调用者不管结果,由被调用者将结果送来)
加锁指令存在互斥,当线程A已经加上锁,线程B尝试去加锁,此处B线程就会进入阻塞等待状态,直到A线程释放锁.(注意当A解除锁后,B也不定能加锁成功,还要看其他线程会不会与线程竞争)
synchronized的4种加锁(synchronized加锁就是修改对象头)
例一:对方法加锁
public synchronized void func(){}
对func方法加锁, 在线程a调用该方法后,线程b跟着调用,那么b因为也要加锁(锁指令有互斥)所以b会进入阻塞状态等待
例二:对对象加锁
public synchronized void func(){
synchronized(this);
}
//方法1和方法2都是对这个对象加锁,本质上是一样的
这里是给调用这个方法的对象加锁
例三:给静态方法加锁(相当于针对类对象加锁)
synchronized static void func(){}
这里是个这个静态方法的类加锁
例四: 直接给类对象加锁,Counter是一个类
public static void func(){
synchronized(Counter.class){
}
}
//方法三和方法四可以看成是对类对象加锁,本质一样
2.内存可见性问题,指令重排序问题--->一个线程读一个线程写
(什么是内存可见性问题)------->一个线程读一个线程写
1.volatile关键字 volatile加变量名
这个操作是显式进制编译器进行上述优化,相当于给这个变量加上了内存屏障
3.线程的随机调度顺序,线程饿死问题
1.wait叫做等待,调用wait的线程会进入阻塞等待
2.notify叫做通知/唤醒,调用notify就可以把对应的wait线程唤醒
3.notifyAll(上面是一次唤醒一个,这个是一次唤醒一片)
wait的执行过程:
1.释放锁
2.等待notify
3.当notify后,就会被唤醒,并且尝试获取锁
(所以wait 和 notify调用前要先拿到锁)---->wait必须放到synchronized中使用
synchronized的对象和调用wait方法的对象是同一个对象)
解决线程饿死情况:让线程解决某个问题发现不能把解决是就用wait方法让线程进入阻塞状态,等到条件满足再唤醒