线程安全
一. 线程安全之可见性
1. JVM运行时数据区
2. Java内存模型(Java Memory Model ,JMM) VS JVM运行时数据区
2.1 初看Java内存模型
2.2 多线程中的问题
- 所见非所得, 无法肉眼去检测程序的准确性
- 不同的运行平台有不同的表现
- 错误很难重现
eg 问题:(t1总的线程会一直执行下去为什么呢?jit对这种频繁调用的代码做了优化 会先取demo1.flag 赋值给一个局部变量 然后去判断这个局部变量的值 后面又详细的图)
2.3. 内存缓存
2.4. CPU指令重排
2.5. JIT编译器(Just in time compiler)
脚本语言与编译语言的区别?
解释执行:即咱们说的脚本,在执行时,由语言的解释器将其一条条翻译成机器可识别的指令
编译执行:将我们写的程序,直接编译成机器可以识别的代码。
java是什么语言:解释 编译执行的混合体。
2.6. volatile关键字
public class VisibilityDemo {
// 运行标志
// public volatile boolean flag = true;
/*
*不带volatitle的时候thread1不会停止 jit 做了优化对于循环取值的时候
* boolean aa=demo1.flag
* if(aa){
* while(true){
* i++
* }
* }
*/
public boolean flag = true;
public static void main(String[] args) throws InterruptedException { //思维逻辑上看
VisibilityDemo demo1 = new VisibilityDemo();
System.out.println("代码开始了");
Thread thread1 = new Thread(new Runnable() {
public void run() {
int i = 0;
while (demo1.flag) {
i++;
}
System.out.println(i);
}
});
thread1.start();
TimeUnit.SECONDS.sleep(2); //JMM 必要性
// 设置is为false,使上面的线程结束while循环
demo1.flag = false;
System.out.println("被置为false了.");
}
}
2.7.shared variables 定义
2.8. 线程间操作的定义
2.9. 对于同步的规则定义
对于监视器m的解锁与所有后续操作对于m的加锁同步
–即1.加锁解锁不能进行指令重排 2.加锁后的操作,对于解锁后的其他获取锁的线程内容可见
对于每个属性写入的默认值(0,false,null)与每个线程对其进行的操作同步
new 一个对象初始值会是乱码,然后是默认的0,false等,然后再赋值50,james等。
启动线程的操作与线程中的第一个操作同步
– 启动线程的第一个操作 即将线程的状态由NEW改为Runnable. 即只有t2线程状态变更为Runnble的时候才能执行t2线程的第一行代码
线程T2的最后操作与线程T1发现线程T2已经结束同步。(isAlive,join可以判断线程时否终止)
如果线程T1中断了T2,那么线程T1的中断操作与其他所有线程发现T2被中断了同步
通过抛出InterruptedException异常,或者调用Thread.interrupted或Thread.isInterrupted.
2.10 Happens-before先行发生原则
2.11 final 在jvm中的处理–没看懂 但是感觉不重要此处进记录
2.12 word tearing 字节处理
2.13 double和long的特殊处理
二. 线程安全之原子操作
1. 概念
2. CAS (Compare and swap)
public class CounterUnsafe {
volatile int i = 0; //cas 硬件 内存地址 --Long 232323523454235
private static Unsafe unsafe =null;//Unsafe类时final修饰的
private static long valueOffSet;// 初始话的时用来存储 变量i对应的内存地址
static {
try {
// unsafe = Unsafe.getUnsafe(); **该方法禁止程序直接调用会抛出异常 , 对于属性静止调用的我们可以通过发射机制来获取值**
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
Field field1 = CounterUnsafe.class.getDeclaredField("i");
valueOffSet = unsafe.objectFieldOffset(field1);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
public void add() {
for(;;) {
int current = unsafe.getIntVolatile(this, valueOffSet); //**性能 : 分析 解决原子性问题 方式有: 阻塞 或 自旋(cas) 分别损失的是谁? 自旋属于计算损失cpu 阻塞hunt住线程 损失内存(某种意义上也会损失cpu)**
//**系统中 内存 和 cpu 哪个更容易出现异常或 100% ?cpu更容易出事 内存出问题大概率是程序的问题 ---更多情况下我们会选择损耗内存**
if (unsafe.compareAndSwapInt(this, valueOffSet, current, current + 1)){ //要么成功,要么失败
break;
};
}
}
}
2.1 JUC包内的原子操作封装类
public class CounterAtomic {
//volatile int i = 0;
AtomicInteger i = new AtomicInteger(0); //封装了CAS机制.
public void add() {
i.incrementAndGet();
}
}
2.3 CAS的三个问题
ABA 问题是由于仅仅比较值的时候可能在并发情况下与预期不一致的结果,解决方案:添加版本号
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
// 实现一个 栈(后进先出)
public class Stack {
// top cas无锁修改
AtomicReference<Node> top = new AtomicReference<Node>();
public void push(Node node) { // 入栈
Node oldTop;
do {
oldTop = top.get();
node.next = oldTop;
}
while (!top.compareAndSet(oldTop, node)); // CAS 替换栈顶
}
// 出栈 -- 取出栈顶 ,为了演示ABA效果, 增加一个CAS操作的延时
public Node pop(int time) {
Node newTop;
Node oldTop;
do {
oldTop = top.get();
if (oldTop == null) { //如果没有值,就返回null
return null;
}
newTop = oldTop.next;
if (time != 0) { //模拟延时
LockSupport.parkNanos(1000 * 1000 * time); // 休眠指定的时间
}
}
while (!top.compareAndSet(oldTop, newTop)); //将下一个节点设置为top
return oldTop; //将旧的Top作为值返回
}
}
public class ConcurrentStack {
// top cas无锁修改
//AtomicReference<Node> top = new AtomicReference<Node>();
AtomicStampedReference<Node> top =
new AtomicStampedReference<>(null, 0);
public void push(Node node) { // 入栈
Node oldTop;
int v;
do {
v = top.getStamp();
oldTop = top.getReference();
node.next = oldTop;
}
while (!top.compareAndSet(oldTop, node, v, v+1)); // CAS 替换栈顶
}
// 出栈 -- 取出栈顶 ,为了演示ABA效果, 增加一个CAS操作的延时
public Node pop(int time) {
Node newTop;
Node oldTop;
int v;
do {
v = top.getStamp();
oldTop = top.getReference();
if (oldTop == null) { //如果没有值,就返回null
return null;
}
newTop = oldTop.next;
if (time != 0) { //模拟延时
LockSupport.parkNanos(1000 * 1000 * time); // 休眠指定的时间
}
}
while (!top.compareAndSet(oldTop, newTop, v, v+1)); //将下一个节点设置为top
return oldTop; //将旧的Top作为值返回
}
}
// 存储在栈里面元素 -- 对象
public class Node {
public final String value;
public Node next;
public Node(String value) {
this.value = value;
}
@Override
public String toString() {
return "value=" + value;
}
}
// 实现一个 栈(后进先出)
public class Stack {
// top cas无锁修改
AtomicReference<Node> top = new AtomicReference<Node>();
public void push(Node node) { // 入栈
Node oldTop;
do {
oldTop = top.get();
node.next = oldTop;
}
while (!top.compareAndSet(oldTop, node)); // CAS 替换栈顶
}
// 出栈 -- 取出栈顶 ,为了演示ABA效果, 增加一个CAS操作的延时
public Node pop(int time) {
Node newTop;
Node oldTop;
do {
oldTop = top.get();
if (oldTop == null) { //如果没有值,就返回null
return null;
}
newTop = oldTop.next;
if (time != 0) { //模拟延时
LockSupport.parkNanos(1000 * 1000 * time); // 休眠指定的时间
}
}
while (!top.compareAndSet(oldTop, newTop)); //将下一个节点设置为top
return oldTop; //将旧的Top作为值返回
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Stack stack = new Stack();
//ConcurrentStack stack = new ConcurrentStack();
stack.push(new Node("B")); //B入栈
stack.push(new Node("A")); //A入栈
Thread thread1 = new Thread(() -> {
Node node = stack.pop(800);
System.out.println(Thread.currentThread().getName() +" "+ node.toString());
System.out.println("done...");
});
thread1.start();
Thread thread2 = new Thread(() -> {
LockSupport.parkNanos(1000 * 1000 * 300L);
Node nodeA = stack.pop(0); //取出A
System.out.println(Thread.currentThread().getName() +" "+ nodeA.toString());
Node nodeB = stack.pop(0); //取出B,之后B处于游离状态
System.out.println(Thread.currentThread().getName() +" "+ nodeB.toString());
stack.push(new Node("D")); //D入栈
stack.push(new Node("C")); //C入栈
stack.push(nodeA); //A入栈
System.out.println("done...");
});
thread2.start();
LockSupport.parkNanos(1000 * 1000 * 1000 * 2L);
System.out.println("开始遍历Stack:");
Node node = null;
while ((node = stack.pop(0))!=null){
System.out.println(node.value);
}
}
}
2.4 线程安全的概念
可见性问题
原子性问题
2.5 共享资源
三. java锁相关
3.1 java中锁的概念
3.2 synchronized
1. 锁优化
2. 锁的原理
3. 锁的升级过程
4. 对象头详解
https://zhuanlan.zhihu.com/p/138582291
下面这篇文章貌似讲的更为详细 可以参考
https://blog.csdn.net/tongdanping/article/details/79647337
这篇文章讲解了如何在cpu高的情况下找到对应的线程然后根据Jstack来查看问题
https://www.cnblogs.com/wuchanming/p/7766994.html
https://www.jianshu.com/p/9e02bed1387c