并发编程(三)
2022-09-22 22:47:33

hopeStation/thread-learn

Stream流式计算

异步回调(Future)

设计的初衷:对将来的某个时间的结果进行建模

JMM

JMM即为JAVA 内存模型(java memory model),是一个概念

关于JMM的一些约定

  1. 线程解锁前
    必须把共享变量立刻刷回主存
  2. 线程加锁前
    必须把读取主存中的最新值到线程的工作内存
  3. 加锁和解锁 是同一把锁

八个操作

image20201126165607193.png

八种线程之间的交互指令

  1. lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状 。
  2. unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  3. read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
  4. load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  5. use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
  6. assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  7. store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
  8. write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。

JMM对交互指令的约束

如果要把一个变量从主内存中复制到工作内存,就需要按顺寻地执行read和load操作,如果把变量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。

Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。也就是read和load之间,store和write之间是可以插入其他指令的,如对主内存中的变量a、b进行访问时,可能的顺序是read a,read b,load b, load a。Java内存模型还规定了在执行上述八种基本操作时,必须满足如下规则:

  • 不允许read和load、store和write操作之一单独出现
  • 不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。
  • 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。
  • 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
  • 一个变量在同一时刻只允许一条线程对其进行lock操作,lock和unlock必须成对出现
  • 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值
  • 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
  • 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。

volatile

提供轻量级的同步机制

  • 保证可见性
  • 不保证原子性
  • 禁止指令重排

什么是指令重排

你写的程序,计算机并不是按照你写的那样去执行的

源代码 -> 编译器优化的重排 -> 指令并行也可能会重排 -> 内存系统也会重排 -> 执行

1
2
3
4
int x = 5;//1
int y = 5;//2
x = x + 1;//3
y = y + x; //4

真正的的执行顺序不一定是 1234,有可能是 2134,1324

但不可呢是 1243 ,因为 处理器在进行指令重排时,会考虑数据之间的依赖性,不会影响正确的结果

但是在多线程的情况下,可能会出错,举个例子:

x,y,a,b的初始值都是0

顺序 线程A 线程B
先执行 x=a y=b
后执行 b=1 a=2

正确的执行结果 x=0,y=0

指令重排后的顺序也可能变成

顺序 线程A 线程B
先执行 b=1 a=2
后执行 x=a y=b

正确的执行结果 x=2,y=1

volatile可以避免指令重排

  1. 保证特定操作的执行顺序
  2. 可以保证某些变量的内存可见性

CAS

坑:

Integer(-128~127比较特殊)

死锁

死锁指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程

image20201130103019041.png

例子

不推荐使用String类型 作为锁对象

String对象存在于常量池中,相同内容的String对象地址相同,为同一对象。因此如果使用String作为锁时,如果之前设置String对象锁的值与后面设置的锁对应的String对象值相同则会影响代码的执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
* 死锁
*/
public class MyDeadLock implements Runnable{
boolean flag;
/**
* 必须是static的,
* 在main方法里,创建了两个对象,如果不是静态的,就变成了每一个对象都有o1 o2,无法造成死锁
* static来保证:两个线程 来争夺这相同的这两个对象
*/
static Object o1 = new Object();
static Object o2 = new Object();
public MyDeadLock(boolean flag){
this.flag = flag;
}
@Override
public void run(){
if(this.flag){
synchronized (o1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " get lock o1");
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " get lock o2");
}
}
}
else{
synchronized (o2) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " get lock o2");
synchronized (o1) {
System.out.println(Thread.currentThread().getName() + " get lock o1");
}

}
}
}
}
class Test {

public static void main(String[] args) {
// TODO Auto-generated method stub
Thread t1 = new Thread(new MyDeadLock(true));
Thread t2 = new Thread(new MyDeadLock(false));
t1.start();
t2.start();

}

}

排查死锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#查看进程号
C:\Program Files\Java\jdk1.8.0_181\bin>jps -l
7952 lock.Test
...

# jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息
C:\Program Files\Java\jdk1.8.0_181\bin>jstack 7952
...
#发现了死锁
#Found one Java-level deadlock
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x00000000575bae38 (object 0x00000000d70b8190, a java.
lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x00000000575b8448 (object 0x00000000d70b81a0, a java.
lang.Object),
which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
at lock.MyDeadLock.run(MyDeadLock.java:45)
- waiting to lock <0x00000000d70b8190> (a java.lang.Object)
- locked <0x00000000d70b81a0> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"Thread-0":
at lock.MyDeadLock.run(MyDeadLock.java:31)
- waiting to lock <0x00000000d70b81a0> (a java.lang.Object)
- locked <0x00000000d70b8190> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
#Found 1 deadlock.
Found 1 deadlock.


排查问题:

  • 查看日志
  • 查看堆栈信息