package com.gjw._nowcoder;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Main {
private static Lock lock = new ReentrantLock();
private static int state = 0;
static class ThreadA implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
lock.lock();
state++;
System.out.println("A" + state);
lock.unlock();
}
}
}
static class ThreadB implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
lock.lock();
state++;
System.out.println("B" + state);
lock.unlock();
}
}
}
public static void main(String[] args) {
new Thread(new ThreadA()).start();
new Thread(new ThreadB()).start();
}
}
A1
A2
A3
A4
A5
B6
B7
B8
B9
B10
思考一个问题,在循环中,run方法中state应该为线程的工作内存值,那为什么两个线程对state的操作具有可见性了呢?
线程A会首先先从主内存中读取共享变量state=0的值然后将该变量拷贝到自己的工作内存,进行加一,打印操作后,再将该值刷新到主内存,整个过程即为线程A 加锁–>执行临界区代码–>释放锁相对应的内存语义。
线程B获取锁的时候同样会从主内存中共享变量a的值,这个时候就是最新的值1,然后将该值拷贝到线程B的工作内存中去,释放锁的时候同样会重写到主内存中。
从整体上来看,线程A的执行结果(a=1)对线程B是可见的,实现原理为:释放锁的时候会将值刷新到主内存中,其他线程获取锁时会强制从主内存中获取最新的值。
从横向来看,这就像线程A通过主内存中的共享变量和线程B进行通信,A 告诉 B 我们俩的共享数据现在为1啦,这种线程间的通信机制正好吻合java的内存模型正好是共享内存的并发模型结构。
锁的内存语义
锁是Java并发编程中最重要的同步机制。锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息。
语义:
当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。锁释放与volatile写具有相同的内存语义。
当线程获取锁时,JMM 会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须要从主内存中去读取共享变量。锁获取与volatile读具有相同的内存语义。
锁释放和锁获取的内存语义总结:
线程A释放一个锁,实质上是线程A向接下来将要获取这个锁的某个线程发出了(线程A对共享变量所做修改的)消息。
线程B获取一个锁,实质上是线程B接收了之前某个线程发出的(在释放这个锁之前对共享变量所做修改的)消息。
线程A释放锁,随后线程B获取这个锁,这个过程实质上是线程A通过主内存向线程B发送消息。