volatile关键字,wait和notify

文章详细解释了Java中的volatile关键字如何解决内存可见性问题,以及wait和notify方法在线程同步中的作用。volatile确保变量在多线程环境中的可见性,但不保证原子性。wait和notify用于线程间的通信,控制执行顺序,wait会使线程进入WAITING状态并释放锁,而notify会唤醒等待同一锁的线程。
摘要由CSDN通过智能技术生成

目录

volatile关键字

内存可见性问题

volatile关键字

JMM(Java内存模型)

wait和notify方法

wait方法

notify 方法(唤醒等待的线程)

wait和sleep的区别

notifyAll


volatile关键字

内存可见性问题

创建两个线程,t1线程用来重复读取数字,当数字不等于0时,t1循环结束,t2线程用来修改数字,我们预期的输出结果是t2把flag改成非0后,t1的循环结束,但是实际输入1的时候,t1这个循环并没有结束循环,并且同时可以看到t2这个线程已经执行完了,t1线程依然还在继续循环,这个情况,就叫做“内存可见性问题”。

import java.util.Scanner;

class MyCounter{
   public int flag = 0;
}
public class ThreadDemo {
    public static void main(String[] args) {

        MyCounter myCounter = new MyCounter();
        Thread t1 = new Thread(() ->{
            //t1要循环快速重复读取
           while(myCounter.flag == 0){
               //循环体
           }
           System.out.println("t1 循环结束");
        });
        Thread t2 = new Thread(()->{
            //t2进行修改
            Scanner scanner = new Scanner(System.in);
           System.out.println("请输入一个整数");
           myCounter.flag = scanner.nextByte();
        });
        t1.start();
        t2.start();
    }
}

t1在进行循环重复读取:1.load把内存中flag的值,读到寄存器里 2.cmp把寄存器的值和0 进行比较,根据比较结果,决定下一步往哪个地方执行,执行这么多次,在t2真正修改之前,load得到的结果都是一样的,另一方面,load操作和cmp操作相比,速度慢非常非常多,CPU针对寄存器的操作,要比内存操作快很多,计算机对于内存的擦欧总,比硬盘快三到四个数量级,由于load执行速度太慢(相比于cmp来说),再加上反复load到的结果都一样。JVM就做出了一个非常大胆的决定,不再重复load操作,判定好像没人改flag值一样,干脆只读一次就好了,所以t2线程在修改flag的值是没用的。

内存可见性问题就是:一个线程针对一个变量进行读取操作,同时另一个线程针对这个变量进行修改,此时读到的值,不一定是修改之后的值,这个线程没有感知到变量的变化,归根结底就是编译器jvm在多线程环境下优化时产生了误判

要改变此问题,只要给flag这个变量就上volatile关键字,意思就是告诉编译器,这个变量时“易变的”,你一定要每次重新读取这个变量的内存内容,不能进行编译器优化  这时候代码就正确了

volatile public int flag = 0;

volatile关键字

只能修饰变量(一个变量在线程中,一个线程读,一个线程写)

volatile 不保证原子性

JMM(Java内存模型)

Java程序中,有主内存,每个线程还有自己的工作内存(t1和t2的工作内存不是同一个东西),t1线程进行读取的时候,只是读取了工作内存的值,t2线程进行修改的时候,先修改的工作内存的值,然后再把工作内存的内容同步到主内存中,但由于编译器优化,导致t1没有重新的从主内存数据到工作内存,读到的结果就是“修改之前”的结果。这里的主存就是内存,工作内存就是指工作存储区 也就是CPU寄存器+CPU缓存 cache。


wait和notify方法

线程最大的问题 是抢占式执行,随机调度,使用join,则必须要t1彻底执行完,t2才能运行,如果是希望t1先干50%的活,就让t2开始行动,join无能为力,使用sleep,指定一个休眠时间,但是t1执行的这些活,到底花了多少时间,不好估计,而wait和notify更好的能控制线程之间的执行顺序,例如有t1和t2两线程,希望t1先干活,干的差不多了,再让t2来干,就可以让t2先wait 阻塞主动放弃CPU 等t1干的差不多了,再通过notify通知t2,把t2唤醒,让t2接着干。

wait方法

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        object.wait();
    }
}

这时候这个线程应该处于一个死等状态,但是当我们运行它的时候发现它爆出了IllegalMonitorStateException异常,意为非法的锁状态,这是为什么呢

wait做的事情

1.先释放锁

2.进行阻塞等待

3.收到通知后,重新尝试获取锁,并且在获取锁后,继续往下执行

就好比去银行自助取款机取钱,但是自助取款机里没有钱了,那我们则要先从自助取款机里出来,再等待工作人员在自助取款机里充值完钱以后,我们收到工作人员的可以取款的消息以后,再进入自助取款机(自助取款机就相当于锁)

而这段代码,我们并没有加锁,直接wait,就没办法解锁,所以wait要搭配synchronized使用

   synchronized (object) {
            object.wait();
        }
    }

wait方法进行阻塞,某个线程调用wait方法,就会进入阻塞(无论是哪个对象wait的),此处就处在WAITING(只要线程中有一个对象wait了 那么这个线程就处在WAITING),虽然这里wait是阻塞了,阻塞在synchronized代码块里,实际上,这里的阻塞是暂时的释放了锁的,此时其他线程是可以获取到object这个对象的锁的,此时这里的阻塞,就处在WAITING状态

notify 方法(唤醒等待的线程)

public class threadDemo {
    public static void main(String[] args) {
        Object object = new Object();
        Thread t1 = new Thread(() ->{
            System.out.println("t1:wait 之前");
            try {
                synchronized (object) {
                    object.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1:wait之后");
        });
        Thread t2 = new Thread(() ->{
            System.out.println("t2:notify 之前");
            synchronized (object) {
                object.notify();
            }
            System.out.println("t2:notify 之后");
        });
        t1.start();
        try {
            Thread.sleep(500);
            //因为线程的随机调度 所以不确定t1和t2谁先被调度
            //但是notify必须要在取到锁才能进行通知,所以
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

😃 notify使用的对象必须和wait使用的对象一样,如果有多个线程等待,那么线程调度器会随机挑选一个呈wait状态的线程。

😃在notify()方法之后,当前线程不会马上释放该对象锁,要等执行完notify()方法的线程将程序执行完,才会释放对象锁。

如果调用notify,此时没有人wait,那么就无法唤醒wait,相当于notify空喊了一下,没有副作用例如小明去食堂买黄焖鸡,付了款以后又去别的地方转悠,等他的饭好了,食堂师傅喊号取餐,但是没有人去取餐,但是也没有影响,这里的食堂师傅喊号就是notify()唤醒等待线程。

wait和sleep的区别

不同点:一个是用于线程之间的通信的,一个是让线程阻塞一段时间
相同点:都能让线程放弃执行一段时间

😀wait的带等待时间版本,看起来和sleep有点像,虽然都能指定等待时间,虽然也能被提前唤醒(wait使用notify唤醒,sleep使用interrupt唤醒)但是这里表示的含义截然不同,notify唤醒外套,这里不会有任何异常,interrupt唤醒sleep则是出异常了,wait是object的方法,sleep是Thread的静态方法


notifyAll

notifyAll和notify非常相似,多个线程wait的时候,notifyAll所有线程都唤醒,这些线程再一起竞争锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值