对JUC并发编程的理解
什么是并发
//多个线程同时调用一个对象的时候出现并发问题
//买火车票
public class Demo01 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
//创建线程A调用买票方法
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket.saleTicket();
}
},"A").start();
//创建线程B调用买票方法
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket.saleTicket();
}
},"B").start();
//创建线程C调用买票方法
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket.saleTicket();
}
},"C").start();
}
}
class Ticket{
private int ticketNum=50;
public void saleTicket(){
if (ticketNum>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticketNum--+"张票,剩余"+ticketNum+"张票");
}
}
}
运行结果:
我们想要的结果是线程一个一个有秩序的排队去买票,但是这里相当于几个线程同时去买同一张票,这就是并发问题
想要解决这个问题我们常用的方法就是在买票的方法上加上一个synchronized关键字。
当我们加上synchronized锁之后再看运行结果
class Ticket{
private int ticketNum=50;
public synchronized void saleTicket(){
if (ticketNum>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticketNum--+"张票,剩余"+ticketNum+"张票");
}
}
}
可以看出加上synchronized锁之后的运行记过就是我们想要的结果——每个线程一张一张有秩序的买票
传统的synchronized锁
synchronized 本质就是队列和锁:
就相当于排队买东西需要拿上锁才能买,当线程A进去买东西的时候就会拿上锁,其他线程没有锁就不能买,当线程A买完东西后就会把锁给释放,然后下一个进去买东西由拿上锁,这样以此内推,就会形成一个有秩序的队列。
Lock锁
我们常用的是ReentrantLock(可重入锁)看源码
-
公平锁:顾名思义它十分公平,先来后到,必须得排队
如果线程A需要3分钟执行完,线程B3s秒钟执行完,如果用公平锁要是线程A先来就算线程B只要3秒也得等3分钟的线程A先执行完。所以为了系统运行效率默认非公平锁
-
非公平锁:十分不公平,可以插队(默认)
用法很简单:
一般将要锁的业务代码快用try-catch-finally包含解锁的操作放在finally里面
public class Demo02 {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket.saleTicket();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket.saleTicket();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket.saleTicket();
}
},"C").start();
}
}
class Ticket2{
private int ticketNum=50;
public void saleTicket(){
Lock lock = new ReentrantLock();
//上锁
lock.lock();
try {
//业务代码
if (ticketNum>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticketNum--+"张票,剩余"+ticketNum+"张票");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//解锁
lock.unlock();
}
}
}
Lock锁的三部曲:
-
创建lock对象:
Lock lock = new ReentrantLock()
-
加锁:
lock.lock()
-
解锁:在finally块中
lock.unlock()
Lock锁和synchronized锁相比有个很简单比方:synchronized是自动挡,Lock是手动挡
synchronized和Lock区别
Lock锁和synchronized锁相比有个很简单比方:synchronized是自动挡,Lock是手动挡
- synchronized锁是内置关键字,Lock锁是一个java类
- synchronized锁无法判断获取锁的状态,Lock可以判断是否获取到了锁
- synchronized锁会自动释放锁,lock必须手动释放锁,如果不释放就会发生死锁
- synchronized锁如果线程A拿到锁后发生了阻塞,那么其他线程只能傻傻的等待;Lock就不一定会等下去,Lock有一个tryLock()方法——尝试获取锁,在等待的时间内如果线程是空闲的并且没有获得锁那么就会尝试获取锁。
- synchronized锁可重入锁,非公平的(自动的,不可设置),而Lock锁可以手动设置非公平还是公平
生产者消费者问题
三部曲:
1:判断是否需要等待,执行等待操作
2:干活———执行相关业务
3:通知其他线程活干完了
public class Dome01 {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
try {
for (int i = 0; i < 30; i++) {
data.increment();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程A").start();
new Thread(()->{
try {
for (int i = 0; i < 30; i++) {
data.decrement();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程B").start();
}
}
/*
步骤1:判断是否需要等待,执行等待操作
2:干活———执行相关业务
3:通知其他线程活干完了
*/
class Data{
private int number=0;
//+1操作
public synchronized void increment() throws InterruptedException {
//判断是否需要等待
if (number!=0){
//执行等待操作
this.wait();
}
//干活
number++;
System.out.println(Thread.currentThread().getName()+"==>>"+number);
//+1完成后,需要通知其他线程+1完成了
this.notifyAll();
}
//-1操作
public synchronized void decrement() throws InterruptedException {
if (number==0){
//执行等待操作
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"==>>"+number);
//通知其他线程-1完成
this.notifyAll();
}
}
按理说线程ABCD应该执行+1,-1,再+1,-1这样交替执行,只会有1和0不会有2或者3,这就是虚假唤醒
上面操作用if判断的话会出现一个虚假唤醒的问题,因为线程多个线程操作一个资源类的时候if只会判断一次,将if改成while就能解决
/*
步骤1:判断是否需要等待,执行等待操作
2:干活———执行相关业务
3:通知其他线程活干完了
*/
class Data{
private int number=0;
//+1操作
public synchronized void increment() throws InterruptedException {
while (number!=0){
//执行等待操作
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"==>>"+number);
//+1完成后,需要通知其他线程+1完成了
this.notifyAll();
}
//-1操作
public synchronized void decrement() throws InterruptedException {
while (number==0){
//执行等待操作
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"==>>"+number);
//通知其他线程-1完成
this.notifyAll();
}
}
将if换成while后运行结果:
JUC版的生产者消费者问题
官方文档中给出Lock锁的等待和唤醒操作:
代码:
package com.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created with Intellij IDEA
*
* @Auther:龙本淼
* @date:2022/08/11/21:01 Description:
*/
public class Dome02 {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(()->{
try {
for (int i = 0; i < 20; i++) {
data.increment();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程A").start();
new Thread(()->{
try {
for (int i = 0; i < 20; i++) {
data.decrement();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程B").start();
new Thread(()->{
try {
for (int i = 0; i < 20; i++) {
data.increment();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程C").start();
new Thread(()->{
try {
for (int i = 0; i < 20; i++) {
data.decrement();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程D").start();
}
}
/*
步骤1:判断是否需要等待,执行等待操作
2:干活———执行相关业务
3:通知其他线程活干完了
*/
class Data2{
private int number=0;
final Lock lock=new ReentrantLock();
Condition condition=lock.newCondition();
//+1操作
public void increment() throws InterruptedException {
lock.lock();
try {
while (number!=0){
//执行等待操作
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"==>>"+number);
//+1完成后,需要通知其他线程+1完成了
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//-1操作
public synchronized void decrement() throws InterruptedException {
lock.lock();
try {
while (number==0){
//执行等待操作
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"==>>"+number);
//通知其他线程-1完成
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
精准通知和唤醒线程
Condition里面的condition.signalAll();是唤醒全部线程,所以我们可以创建几个Condition监视器分别监视不同的线程,就可以精确唤醒不同的线程——代码实现:
public class Demo03 {
public static void main(String[] args) {
Data3 data3 = new Data3();
new Thread(()->{
for (int i = 0; i < 20; i++) {
data3.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
data3.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
data3.printC();
}
},"C").start();
}
}
class Data3{
private Lock lock=new ReentrantLock();
Condition conditionA=lock.newCondition();
Condition conditionB=lock.newCondition();
Condition conditionC=lock.newCondition();
private int number=1;
public void printA() {
lock.lock();
try {
//判断是否需要等待
while (number!=1){
conditionA.await();
}
//干活
System.out.println("线程"+Thread.currentThread().getName()+"==>>"+number);
//将number变成2
number=2;
//唤醒线程B
conditionB.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while (number!=2){//判断线程B是否需要等待
conditionB.await();
}
//干活,
System.out.println("线程"+Thread.currentThread().getName()+"==>>"+number);
//将number变成3
number=3;
//唤醒线程C
conditionC.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while (number!=3){//判断线程C是否需要等待
conditionC.await();
}
//干活,
System.out.println("线程"+Thread.currentThread().getName()+"==>>"+number);
//将number变成1
number=1;
//唤醒线程A
conditionA.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
深刻理解锁
通过代码深刻理解
原因:因为synchronized锁它所得对象是方法的调用者,由于我们值new了一个Phone对象,所以两个线程拿的是同一把锁,谁先拿到锁谁先运行
再看看new两个Phone对象,两个对象分别调用不同方法的运行结果:
原因是因为两个方法的调用者是不一样的一个是phone1,一个phone2,所以拿的不是同一把锁,所以谁快谁先运行完
总结:要知道谁先运行完,最终要看两个线程拿的时不时同一把锁,如果是,那就是谁先拿到锁谁就先运行完,如果不是,就谁运行快谁先运行完
线程不安全的集合
ArrayList及其解决方案CopyOnWriteArrayList
怎么看出ArrayList不安全,代码如下:
解决方案一:Vector集合是线程安全的,可以解决,代码如下:
解决方案二:Collections.synchronizedList(list)
可以将ArrayList集合编程同步安全的
解决方案三:在JUC包下有一个类:java.util.concurrent.CopyOnWriteArrayList<E>
CopyOnWrite:写入时复制
就是在写入的时候将它复制一份给调用者,调用者写完后又放回来,在写入的时候避免覆盖造成数据问题。
为什么不推荐使用Vector集合,它和CopyOnWriteArrayList区别:
Vector的add()方法底层源码是用的synchronized同步代码块,而CopyOnWriteArrayList是用的lock锁。
HashSet及其解决方案CopyOnWriteArraySet
解决方案一:Collections.synchronizedSet(new HashSet<>())
解决方案二:new CopyOnWriteArraySet()
HashSet底层
HashSet底层就是HashMap
add方法就是map的Key,所以HashSet是无法重复的,而且是无序的
HashMap及其解决方案ConcurrentHashMap
HashMap底层原理另一篇博客:HashMap底层原理
解决方案一:Collections.synchronizedMap(new HashMap<>())
解决方案二:new ConcurrentHashMap()
Callable
- 有返回值
- 可以抛出异常
- 方法不同:Callable是call()方法;Runnable是run()方法
代码试例:
使用new Thread().start();
方法有个问题:
所以要想如何让Callable与Runnable接口挂钩;
FutureTask类:
所以只需要如图就能实现Callable接口的线程:
拆解一下可以变成这样:
MyCallable myCallable = new MyCallable();
FutureTask<String> futureTask = new FutureTask<>(myCallable);
new Thread(futureTask).start();
获取Callable的返回值:FutureTask的get()方法可以获得返回值。
Callable执行效率高;因为结果会被缓存代码示例:
JUC常用辅助类
CountDownLatch
代码:
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 1; i <= 10; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() +"线程走了");
countDownLatch.countDown();//数量-1
},String.valueOf(i)).start();
}
System.out.println("关门了。。。。。");
}
}
解决办法代码示例:
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 1; i <= 10; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() +"线程走了");
countDownLatch.countDown();//数量-1
},String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println("关门了。。。。。");
}
}
CyclicBarrier
代码示例(就想当于上面那个辅助类是减法计数器,这个是加法计数器):
public class CyclicBarrierTest {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("第"+Thread.currentThread().getName()+"线程执行了屏障跳闸给定的动作");
});
for (int i = 1; i <= 7; i++) {
try {
TimeUnit.NANOSECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
System.out.println("第"+Thread.currentThread().getName()+"个线程运行了");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
Semaphore
Semaphore:信号量
public class SemaphoreTest {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);//设置3个信号量
for (int i = 1; i <= 6; i++) {
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"拿到信号量,阻塞");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"拿到许可证,释放阻塞");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
原理:
==semaphore.acquire();
==获得信号量,如果满了就等待,知道信号量被释放,有空余信号量为止
==semaphore.release();
==释放信号量,会将当前信号量释放,然后唤醒等待线程。
作用:多个线程共享资源互斥使用,并发限流,控制最大的线程数量
ReadWriteLock读写锁
没有加读写锁:
public class ReadWriteLockTest {
public static void main(String[] args) {
ReadWrite readWrite = new ReadWrite();
//读取操作
for (int i = 1; i <= 5; i++) {
int temp=i;
new Thread(()->{
readWrite.write(temp+"",temp);
},String.valueOf(i)).start();
}
//读取操作
for (int i = 1; i <= 5; i++) {
int temp=i;
new Thread(()->{
readWrite.read(temp+"");
},String.valueOf(i)).start();
}
}
}
class ReadWrite{
private Map<String,Object> map=new HashMap<>();
public void write(String key,Object value){
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key, value);
System.out.println(Thread.currentThread().getName()+"写入完成");
}
public void read(String key){
System.out.println(Thread.currentThread().getName()+"读取了"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取完成");
}
}
创建读写锁:
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();//实例化读写锁对象
readWriteLock.writeLock().lock();//上写锁
readWriteLock.writeLock().unlock();//释放写锁
readWriteLock.readLock().lock();//上读锁
readWriteLock.readLock().unlock();//释放读锁
加上读写锁之后:
public class ReadWriteLockTest {
public static void main(String[] args) {
ReadWrite readWrite = new ReadWrite();
//读取操作
for (int i = 1; i <= 5; i++) {
int temp=i;
new Thread(()->{
readWrite.write(temp+"",temp);
},String.valueOf(i)).start();
}
//读取操作
for (int i = 1; i <= 5; i++) {
int temp=i;
new Thread(()->{
readWrite.read(temp+"");
},String.valueOf(i)).start();
}
}
}
class ReadWrite{
private Map<String,Object> map=new HashMap<>();
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void write(String key,Object value){
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key, value);
System.out.println(Thread.currentThread().getName()+"写入完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
public void read(String key){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"读取了"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
总结:
独占锁(写锁):一次只能被一个线程占用。
共享锁(读锁):多个线程可以同时占用。
线程池
线程池的好处
线程池一直开启,无需创建销毁,用完归还计科,统一管理,防止内存溢出
- 降低资源的消耗(对象的创建和销毁十分浪费资源)
- 提高响应的速度
- 方便管理
线程复用,可以控制最大并发数,可以管理线程
线程池的3大方法,7大参数,4种拒绝策略
3大方法(阿里巴巴开发手册规定一般用此方法创建线程池)
// ExecutorService executorPool = Executors.newSingleThreadExecutor();//单个线程
// ExecutorService executorPool = Executors.newFixedThreadPool(5);//固定的线程池大小
// ExecutorService executorPool = Executors.newCachedThreadPool();//可伸缩的,遇强则强遇弱则弱
newSingleThreadExecutor()方法:
newFixedThreadPool(int Threads)方法
newCachedThreadPool()方法
点开这三个方法的源码可以看出:
这3个方法的本质都是new 一个ThreadPoolExecutor()这个方法
7大参数
new ThreadPoolExecutor()的7大参数(源码):
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
-
如果此时线程池中的线程数量小于corePoolSize,无论线程池中的线程是否处于空闲状态,也会创建新的线程来处理被添加的任务;
-
如果此时线程池中的线程数量等于corePoolSize,但是阻塞队列workQueue未满,那么任务被放入阻塞队列;
-
如果此时线程池中的线程数量大于等于corePoolSize,阻塞队列workQueue已满,并且线程池中的线程数量小于maximumPoolSize,则会创建新的线程来处理被添加的任务;
-
如果此时线程池中的线程数量大于corePoolSize,阻塞队列workQueue已满,并且线程池中的线程数量等于maximumPoolSize,那么通过 handler所指定的拒绝策略来处理此任务;
-
当线程池中的线程数量比corePoolSize数量要多时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。
图解如下:
JMM理解
什么是JMM
JMM 即为 Java 内存模型(Java Memory Model)。是一种抽象的概念并不真实存在,它描述的是一组规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式
JMM 关于同步的规定:
- 线程加锁之前,必须读取主内存的最新值到自己的工作内存
- 线程解锁之前,必须把共享变量的值刷新回主内存
- 加锁解锁是同一把锁
JMM的三大特性:原子性、可见性、有序性。
解释说明
在JVM中,栈负责运行(主要是方法),堆负责存储(比如new的对象)。由于JVM运行程序的实体是线程,而每个线程在创建时,JVM都会为其创建一个工作内存(有些地方称为栈空间),工作内存是每个线程的私有数据区域。而JAVA内存模型中规定,所有变量都存储在主内存中,主内存是共享内存区域,所有线程都可以访问。
但线程对变量的操作(读取赋值等)必须在自己的工作内存中进行。首先要将变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成后,再将变量写回到主内存。由于不能直接操作主内存中的变量,各个线程的工作内存中存储着主内存中的变量副本,因此,不同的线程之间无法直接访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成。
Java 内存模型定义了八种操作来实现:
lock:锁定。作用于主内存的变量,把一个变量标识为一条线程独占状态。
unlock:解锁。作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
read:读取。作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
load:载入。作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
use:使用。作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
assign:赋值。作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
store:存储。作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
write:写入。作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。
指令重排
代码解析:
int x = 1;//1
int y = 2;//2
x = x + 5;//3
y = x * x;//4
我们想要的执行顺序是1234,但是计算机执行的时候可能会指令重排顺序编程1324,或者2143。
由于处理器在进行指令重排的时候会考虑数据之间的依赖性,所以不可能是4321,因为变量需要先定义再使用
Volatile关键字
- 保证可见性
- 不保证原子性
- 禁止指令重排
己的工作内存中进行。首先要将变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成后,再将变量写回到主内存。由于不能直接操作主内存中的变量,各个线程的工作内存中存储着主内存中的变量副本,因此,不同的线程之间无法直接访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成。
Java 内存模型定义了八种操作来实现:
lock:锁定。作用于主内存的变量,把一个变量标识为一条线程独占状态。
unlock:解锁。作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
read:读取。作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
load:载入。作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
use:使用。作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
assign:赋值。作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
store:存储。作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
write:写入。作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。
[外链图片转存中…(img-rvP2MZ8H-1660722833420)]
指令重排
[外链图片转存中…(img-RSDo7LhH-1660722833421)]
代码解析:
int x = 1;//1
int y = 2;//2
x = x + 5;//3
y = x * x;//4
我们想要的执行顺序是1234,但是计算机执行的时候可能会指令重排顺序编程1324,或者2143。
由于处理器在进行指令重排的时候会考虑数据之间的依赖性,所以不可能是4321,因为变量需要先定义再使用
Volatile关键字
- 保证可见性
- 不保证原子性
- 禁止指令重排