系列:多线程(二)线程安全
文章目录
原子三大特性
原子性
可见性
有序性
模拟不安全问题
原子性
一个对象Count count,10个线程执行+1 100次,输出最后结果(正确:1000)
demo
@Data
class Count {
private int count;
public void add() {
++count;
}
}
void test2() throws InterruptedException {
ThreadPoolExecutor pool = new ThreadPoolExecutor(10, 10, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
Count count = new Count();
for (int i = 0; i < 10; ++i) {
pool.submit(() -> {
for (int j = 0; j < 100; ++j) {
count.add();
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
pool.shutdown();
TimeUnit.SECONDS.sleep(3);
System.out.println(count.getCount());
}
输出
789、813…
解决
synchronized、Lock、原子类…
public synchronized void add() {
++count;
}
public synchronized int getCount() {
return count;
}
可见性
demo
@Setter
class Task extends Thread {
private boolean flag = false;
public boolean isFlag() {
return flag;
}
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println(Thread.currentThread().getName() + " flag = " + flag);
}
}
public class VolatileTest {
public static void main(String[] args) throws InterruptedException {
Task task = new Task();
task.start();
while (true) {
if (task.isFlag()) {
System.out.println("终止了");
break;
}
}
}
}
输出
Thread-0 flag = true
// 主线程没有输出,一直while循环,检测,但是由于flag非volatile,task线程即使修改了flag = true,主线程仍获取不到其值变化
提示:如果主线程在while循环内,加上sleep,就可以检测到flag的变化
解决
private volatile boolean falg = false;
有序性(略)
线程安全
不可变必然安全
-
线程间不共享
- 不共享
-
线程间共享
-
ThreadLocal:线程创建共享变量的副本
-
线程同步
-
原理
CPU
参考:https://juejin.im/post/5c6b99e66fb9a049d51a1094
JMM内存操作
lock和unlock
read和load
use和assign
store和write
happens-before原则
1)程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作。
2)锁定规则:一个unLock操作先行发生于后面对同一个锁的Lock()操作。
3)volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作。
4)传递规则:如果操作A先行发生与操作B,而操作B先行发生于操作C,则操作A先行发生于操作C。
5)线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作。
6)线程终端规则:对线程interrupt()方法的调用先行发生与被中断线程的代码检测到中断事件的发生(只有执行了interrupt()方法才可以检测到中断事件的发生)。
7)线程终结规则:线程中所有操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束,Thread.isAlive()的返回值手段检测到线程已经终止执行。
8)对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始。
内存屏障:禁止重排序
内存屏障前和后的不能乱序,即内存屏障前不能重排序到屏障后,内存屏障后不能重排序到屏障前。
内存屏障:volatile变量读写前后加指令
对象的布局
对象头 + 类型指针 + 实力数据 + 对齐
图源自:https://www.bilibili.com/video/BV1xK4y1C7aT?p=3
volatile
作用:线程可见性;防止指令重排序
-
Java: volatile int i
-
class: ACC_VOLATILE
-
JVM的内存屏障
屏障两端不可以重排
-
Hotspot实现:lock指令
JSR内存屏障:
LoadLoad
StoreStore
LoadStore
StoreLoad
volatile写
StoreStore
volatile 写
StoreLoad
volatile读
LoadLoad
volatile 读
LoadStore
synchronized
作用:同步
锁升级:无锁 --> 偏向锁 --> 轻量级锁 --> 重量级锁
字节码:
语句块:monitorenter和monitorexit
方法:flags加ACC_SYNCHRONIZED(执行方法前先获取monitor,执行完后释放monitor)
monitor
任何一个对象都有一个monitor与之关联,当一个monitor被持有后,对象将处于锁定状态。
synchronized对象被锁,MarkWord的锁标识位为10(重量级锁),其指针指向monitor对象的起始地址。
锁优化
自适应CAS自旋
锁消除
锁粗化
偏向锁,轻量级锁
偏向锁和轻量级锁
偏向锁:ThreadID的CAS
轻量级锁:MarkWord的lock word的CAS更新为当前线程Lock Record的指针,并将Lock Record的owner指针指向锁对象MarkWord
线程栈帧:Lock Record,锁记录,当前锁对象的MarkWord拷贝
重量级锁
涉及底层OS,存在用户态到核心态的切换。
参考
https://juejin.im/post/5c6b99e66fb9a049d51a1094#heading-24
https://blog.csdn.net/u011877584/article/details/81157317
https://www.bilibili.com/video/BV1xK4y1C7aT?p=6
https://juejin.im/post/5b4eec7df265da0fa00a118f