II. 缓存一致性
MESI协议中的缓存状态
状态 | 含义 | 监听任务 |
---|---|---|
M 被修改 Modified |
因为缓存行刚被修改,数据应是独一无二的,与主存中的数据不一致,与其他CPU的缓存中对应数据也不相同。但是一定会在将来某个时间点写回主存中,这个时间点一定是在其他CPU读取自己的(主存相应的)缓存之前。 | 被修改的缓存行必须时刻监听所有想读该缓存行对应的主存/其他CPU缓存的操作,因为要确保在CPU读取操作之前把被修改的缓存行写回主存并将状态变为 S。 |
E 独享的 Exclusive |
被修改状态的缓存行要将数据写回主存,此时可以认为是独享的状态。只有自己的缓存和主存中数据一致,其他CPU对应的缓存行还没有更新。但是一定会在将来其他CPU读取对应的缓存行之前变为共享状态。 | 独享的缓存行也必须监听其他CPU缓存读取主存中对应缓存行的操作,一旦有了这种操作,该缓存行需要变成 S 状态。 |
S 共享的 Shared |
该状态意味该缓存行可能被多个CPU缓存,并且各个缓存中的数据与主存中的数据是一致的。当有一个CPU修改了该缓存行中的数据,该缓存行变为被修改状态,其他CPU中对应的缓存作废,变为无效状态 I。 | 对于该缓存行来说,要监听其他缓存使该缓存行无效。 |
I 无效的 |
Invalid 缓存行作废,无效。 |
Heap(堆):java 里的堆是一个运行时的数据区,堆是由垃圾回收来负责的,堆的优势是可以动态的分配内存的大小,生存期也不必事先告诉编译器,因为他是在运行时动态分配内存的,java的垃圾回收器会定时收走不用的数据,缺点是由于要在运行时动态分配,所有存取速度可能会慢一些。
Stack(栈):栈的优势是存取速度比堆要快,仅次于计算机里的寄存器,栈的数据是可以共享的,缺点是存在栈中的数据的大小与生存期必须是确定的,缺乏一些灵活性
栈中主要存放一些基本类型的变量,比如int,short,long,byte,double,float,boolean,char
对象句柄。
Java内存模型
Java内存模型规定所有的变量都是存在主存当中(类似于前面说的物理内存),每个线程都有自己的工作内存(类似于前面的高速缓存)。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。执行线程必须先在自己的工作线程中对变量i所在的缓存行进行赋值操作,然后再写入主存当中。
原子性:Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。
x = 10; //语句1
y = x; //语句2
x++; //语句3
x = x + 1; //语句4
1)语句1是直接将数值10赋值给x,也就是说线程执行这个语句的会直接将数值10写入到工作内存中。
2)语句2实际上包含2个操作,它先要去读取x的值,再将x的值写入工作内存,虽然读取x的值以及 将x的值写入工作内存 这2个操作都是原子性操作,但是合起来就不是原子性操作了。
3)同样的,x++和 x = x+1包括3个操作:读取x的值,进行加1操作,写入新的值。所以上面4个语句只有语句1的操作具备原子性。
可见性
Java提供了volatile关键字来保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
有序性
在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
在Java里面,可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。
private volatile boolean stop = false;
public void shutDown() {
stop = true;
}
public void doWork() {
while (!stop) {
}
System.out.println("你能读到我们...");
}
public static void main(String[] args) throws InterruptedException {
Volatile01 volatile01 = new Volatile01();
new Thread(() -> {
volatile01.doWork();
}).start();
Thread.sleep(2000);
new Thread(() -> {
volatile01.shutDown();
}).start();
}
运行结果:
你能读到我们...
去掉volatile在去执行
运行结果无输出
方法和代码块(对象锁和类锁):
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的Class对象。
对于同步方法块,锁是Synchonized括号里配置的对象。
public class Sync01 implements Runnable{
static int i = 0;
@Override
public void run() {
add();
}
private synchronized void add(){
for(int j = 0;j < 10000;j++){
i++;
}
}
public static void main(String[] args) throws InterruptedException {
Sync01 sync01 = new Sync01();
Sync01 sync02 = new Sync01();
Thread thread = new Thread(sync01);
Thread thread2 = new Thread(sync02);
thread.start();
thread2.start();
thread.join();
thread2.join();
System.out.println(i);
}
}
结果输出 值<20000
添加static关键字
private synchronized static void add(){
for(int j = 0;j < 10000;j++){
i++;
}
}
结果输出 值=20000
private void add() {
synchronized (Sync01.class) {
for (int j = 0; j < 10000; j++) {
i++;
}
}
}
private void add() {
synchronized (this) {
for (int j = 0; j < 10000; j++) {
i++;
}
}
}
结果输出 值<20000
public class ReentrantLock01 {
static int i = 0;
public ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
ReentrantLock01 reentrantLock01 = new ReentrantLock01();
ReentrantLock01 reentrantLock02 = new ReentrantLock01();
new Thread(() -> {
reentrantLock01.add();
}).start();
//Thread.sleep(1000);
new Thread(() -> {
reentrantLock02.add();
}).start();
Thread.sleep(1000);
System.out.println(i);
}
private void add() {
try {
lock.lock();
for (int j = 0; j < 10000; j++) {
i++;
}
} finally {
lock.unlock();
}
}
}
结果输出 值=20000
public class ReentrantLock04 implements Runnable {
public static ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
try{
if(reentrantLock.tryLock(5, TimeUnit.SECONDS)){
Thread.sleep(6000);
System.out.println("获取");
}else {
System.out.println("获取失败");
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
if(reentrantLock.isHeldByCurrentThread()){
reentrantLock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
ReentrantLock04 reentrantLock04 = new ReentrantLock04();
IntStream.range(0,2).forEach(i->new Thread(reentrantLock04){}.start());
}
}
结果输出
获取失败
获取
Synchronized和ReetrantLock区别
实现方式
Lock是代码级别的,synchronized是JVM级别的
公平
Lock可以是公平所,也可以是不公平锁,默认是非公平锁,synchronized是非公平锁
释放
Lock的释放必须手动调用unlock()方法,而synchronized在代码出了代码块或方法之后就会自动释放锁。
等待中断
Lock中如果持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,但是synchronized不会
条件变量
Lock中可以有多个Condition来实现线程间通信,而synchronized是能通过当前锁来进行线程通信。
什么时候选择用ReetrantLock代替Synchronized
1、 在确实需要一些synchronized锁没有的特性的时候,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者锁投票的时候
2、 优先推荐synchronized开发,如果事实证明synchronized确实不合适,再用ReetrantLock开发。