【Java关键字】volatile、Synchronized

1、并发编程的三大特性

并发编程的三大特性:原子性、可见性、有序性
原子性:一个不可再被分割的颗粒。原子性指的是一个或多个操作要么全部执行成功要么全部执行失败。
有序性: 程序执行的顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排序)
可见性: 一个线程对共享变量的修改,另一个线程能够立刻看到。

2、volatile

它能够使变量在值发生改变时能尽快地让其他线程知道;volatile是java虚拟机提供的轻量级的同步机制。

三大特性如下:
A、保证可见性
B、不保证原子性
C、禁止指令重排
当线程A修改变量值,会将值更新到主内存;且会通知线程B,线程B重新从主内存中读取【volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。】------这种操作会由于网络差,线程B不能及时获取到变量的最新信息。
它能保证变量的可见性,其它线程能看到其最新值【在读取volatile类型的变量时总会返回最新写入的值】,但不能用于实现线程安全的变量自增【如n++】。

2.1、volatile工作原理及作用

在这里插入图片描述
JMM的数据原子操作
Read(读取):从主内存读取数据
Load(载入):将读取到的数据写入工作内存
Use(使用):从工作内存读取数据进行计算
Assign(赋值):将计算好的值重新赋值到工作内存
Store(存储):将工作内存数据写入主内存
Write(写入):将store过去的变量值赋值给主内存

volatile工作原理:每个线程使用volatile变量时首先从主内存读取数据到对应的工作内存。如果某个线程改变volatile变量值,那么这个工作内存的新值会写入到主内存,此时会触发“总线嗅探”机制,对应其他的工作内存上这个变量值失效,所以当线程需要使用这个变量时会重新load加载主内存的变量值,这样保证了变量的可见性。

“非volatile变量修饰的全局变量修改”说明:在多线程下,不同线程都是拥有自己的工作内存,如果某个线程修改了全局变量值,其他线程是不能立即可见,这是因为不同的线程的工作内存不会经过主内存重新加载最新数据。

volatile作用
副本数据修改后立刻写回主内存;
回写时会触发总线嗅探机制;
禁止了指令重排序;

2.2、volatile底层实现原理

内存屏障原理:这涉及到计算系统底层汇编指令。对于一些指令,在指令前加lock前缀,那么CPU是不能对lock前缀位置的前后指令进行重排序。

volatile底层实现原理:volatile修饰的变量对应代码转换为汇编指令,都会带上lock前缀指令,这个指令拥有内存屏蔽功能,保证不重排序。另外,变量变更时,“总线嗅探”机制让其他工作内存的变量值失效,确保各个工作内存重新加载最新变量值。

3、synchronized

关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile。
synchronized的用法:synchronized修饰方法synchronized修饰代码块

3.1、synchronized方法/方法块

public class TestSysnchronized{
          public void test1(){
             sysnchronized(this){
	          //操作}
	}	
	public sysnchronized void test2(){
	   //操作}
	public static void main(){
	  TestSysnchronized ts = new TestSysnchronized();
	  Thread test1 = new Thread(..ts.test1()...);
	  Thread test2 = new Thread(...ts.test2()..);
	  test1.start();
	  test2.start(); 
	}
} 

上面可以看出,mian方法中new一个对象,这个对象实例有两个方法【synchronized方法块和synchronized方法】,由于是一个对象实例,所以是同一个对象锁。main方法new两个线程,所以是两个线程需要该对象锁,那么一个使用一个等待。

3.2、synchronized静态方法/实例方法

public class TestSysnchronized{

    public sysnchronized void test1(){
        //操作}	
	//sysnchronized静态方法
	public static sysnchronized void test2(){
	   //操作}
public static void main(){
	  TestSysnchronized ts = new TestSysnchronized();
	  Thread test1 = new Thread(..ts.test1()...);   ---**获取对象锁**
	  Thread test2 = new Thread(...TestSysnchronized.test2()..);  ---**获取类锁**
	  test1.start();
	  test2.start(); 
	}
}

执行的结果是:线程交替的执行。
类锁对象锁是两个不一样的锁,控制着不同的区域,它们是互不干扰的。同样,线程获得对象锁的同时,也可以获得该类锁,即同时获得两个锁,这是允许的。

3.3、synchronized原理

每个对象有一个监视器锁monitor,monitor被占用属于锁定状态。
Synchronized方法调用,同样是当前执行线程在栈帧建立lock recod锁记录空间,拷贝对象头信息到锁记录空间,开始利用CAS操作去设置,会检验ACC_SYNCHRONIZED标识符是否设置,若该线程能设置成功,那么就获取到该锁所有权,执行线程能执行方法体,执行完成后,释放锁所有权。其他线程能竞争到该锁资源。
Synchronized代码块调用,monitorenter指令插入到同步代码块开始位置,monitorexit指令插入到同步块结束位置。同样的道理,某个执行线程获取到锁权限,那么执行monitorenter指令进入同步块代码,执行结束,执行monitorexit指令来释放锁所有权。

4、wait和notify原理

Object类有wait()和notify()方法。wait()和notify()方法调用的前提是,二者所在的线程体中(即run()方法体)都需要先通过synchronized获取对象的锁。

调用Wait()方法的线程进入synchronized代码块,那么该线程是已经执行了monitorenter指令,当执行到wait()方法,这个线程进入等待队列且释放锁【会退出monitorexit指令】;其他线程调用notify()或notifyAll()后,会通知等待线程唤醒,由于执行notify()方法的线程还是在synchronized代码块锁范围内,必须该线程执行monitorexit指令释放锁后,等待队列的等待线程才开始竞争资源来获取到锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DreamBoy_W.W.Y

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值