synchronized和lock的原理解读以及它们的区别?

3 篇文章 0 订阅

synchronized和lock区别

区别类型synchronizedLock
存在层次Java的关键字,在jvm层面上是JVM的一个接口
锁的获取假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待情况而定,Lock有多个锁获取的方式,大致就是可以尝试获得锁,线程可以不用一直等待(可以通过tryLock判断有没有锁)
锁类型锁可重入、不可中断、非公平可重入、可判断 可公平(两者皆可)
性能少量同步适用于大量同步
支持锁的场景1. 独占锁,在jvm层面上1. 公平锁与非公平锁

synchronized和volatile区别

1) volatile主要应用在多个线程对实例变量更改的场合,刷新主内存共享变量的值从而使得各个 线程可以获得最新的值,线程读取变量的值需要从主存中读取;synchronized则是锁定当前变 量,只有当前线程可以访问该变量,其他线程被阻塞住。另外,synchronized还会创建一个内存屏障,内存屏障指令保证了所有CPU操作结果都会直接刷到主存中(即释放锁前),从而保证 了操作的内存可见性,同时也使得先获得这个锁的线程的所有操作。

  1. volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。 volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞,比如多个线程争抢 synchronized锁对象时,会出现阻塞。

  2. volatile仅能实现变量修改可见性不能保证原子性;而synchronized则可以保证变量的 修改可见性和原子性,因为线程获得锁才能进入临界区,从而保证临界区中的所有语句全部得到 执行。

  3. volatile标记的变量不会被编译器优化,可以禁止进行指令重排;synchronized标记的变量 可以被编
    译器优化。

JMM理论

JMM:Java Memory Model(Java内存模型),根据并发过程中如何处理、可见性、原子性和有序性这三个特性而建立的模型。

可见性:JMM提供了volatile变量定义、final、synchronized块来保证可见性。

原子性:要么全部执行完,要么不执行,synchronized块来保证。

有序性:觉得有序是相对性的,根据从哪个线程观察,volatile和synchronized保证线程之间操作的有序性。

指令重排:处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证各个语句的执行顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。

JMM处理过程:JMM是通过禁止特定类型的编译器重排序和处理器重排序来为程序员提供一致的内存可见性保证。例如A线程具体什么时候刷新共享数据到主内存是不确定的,假设我们使用了同步原语(synchronized,volatile和final),那么刷新的时间是确定的。

在这里插入图片描述

  • 每个线程都有一个自己的本地内存空间–虚拟机栈线程空间。线程执行时,先把变量从主内存读取到线程自己的本地内存空间,然后再对该变量进行操作。
  • 对该变量操作完后,在某个时间再把变量刷新回主内存,所以线程A释放锁后会同步到主内存,线程B获取锁后会同步主内存数据,即“A线程释放锁–B线程获取锁”可以实现A,B线程之间的通信。
    在这里插入图片描述
    假设本地内存A和B有主内存中共享变量a的副本,初始时这三个内存中的a值都为0。线程A在执行时,把更新后的a值(假设值为1)临时存放在自己的本地内存A中。当线程A和线程B需要通信时,线程A首先会把自己本地内存中修改后的a值刷新到主内存中,此时主内存中的a值变为了1。然后,线程B到主内存中去读取线程A更新后的a值,此时线程B的本地内存的a值也变为了1。

synchronized 理解

synchronized作用域:

  1. 修饰实例方法,作用于当前实例,进入同步代码前需要先获取实例的锁
  2. 修饰静态方法,作用于类的Class对象,进入修饰的静态方法前需要先获取类的Class对象的锁
  3. 修饰代码块,需要指定加锁对象(记做lockobj),在进入同步代码块前需要先获取lockobj的锁
class Phone //资源类
{

	public void test(Phone p){
		synchronized(p){ //锁当前对象 this
		 System.out.println("-------test");
		} 
	}
    public synchronized void sendEmail()
    {
    	//锁当前对象 this
        System.out.println("-------sendEmail");
    }

    public static synchronized void sendSMS()
    {
    	//锁当前对象Class模板对象,只有一个
        System.out.println("-------sendSMS");
    }
}

1.实例方法

//同步方法
修饰符 synchronized 返回值类型 方法名(方法参数) { 
	方法体;
}

2.修饰静态方法

//同步静态方法
修饰符 static synchronized 返回值类型 方法名(方法参数) { 
	方法体;
}

3.同步代码块格式

synchronized(锁对象){
	// 访问共享资源的核心代码
}

锁原理

在这里插入图片描述

Monitor 被翻译为监视器或管程

每个 Java 对象都可以关联一个 Monitor 对象,Monitor 也是 class,其实例存储在堆中,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针,这就是重量级锁。

当同步块或同步方法调用该对象时,执行同步块或方法的线程必须先获取到该对象的Monitor所有权,才能进入同步块或同步方法,而这个获取Monitor的过程是排他的,即同一时刻只能有一个线程获取到Monitor,其他线程将被阻塞。

在这里插入图片描述

volatile的理解

使用条件
通过关键字sychronize可以防止多个线程进入同一段代码,在某些特定场景中,volatile相当于一个轻量级的sychronize,因为不会引起线程的上下文切换,但是使用volatile必须满足两个条件:
1、对变量的写操作不依赖当前值,如多线程下执行a++,是无法通过volatile保证结果准确性的;
2、该变量没有包含在具有其它变量的不变式中。

语义
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序

底层原理

lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

2)它会强制将对缓存的修改操作立即写入主存;

3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

可见性
在这里插入图片描述
禁重排

  1. 写指令:
    在这里插入图片描述
  2. 读指令:
    在这里插入图片描述
    适用场景
    1:状态标志
    实现 volatile 变量的规范使用仅仅是使用一个布尔状态标志,用于指示发生了一个重要的一次性事
    件,例如完成初始化或请求停机。
volatile boolean flag;
...
public void shutdown() { flag = true; }
public void doWork() {
while (!flag) {
// do stuff
}

2:DCL双端锁的发布

public class SafeDoubleCheckSingleton
{
    private static volatile SafeDoubleCheckSingleton singleton;
    //私有化构造方法
    private SafeDoubleCheckSingleton(){
    }
    //双重锁设计
    public static SafeDoubleCheckSingleton getInstance(){
        if (singleton == null){
            //1.多线程并发创建对象时,会通过加锁保证只有一个线程能创建对象
            synchronized (SafeDoubleCheckSingleton.class){
                if (singleton == null){
                    //隐患:多线程环境下,由于重排序,该对象可能还未完成初始化就被其他线程读取
                    singleton = new SafeDoubleCheckSingleton();
                }
            }
        }
        //2.对象创建完毕,执行getInstance()将不需要获取锁,直接返回创建对象
        return singleton;
    }
}

3:开销较低的读,写锁策略

public class UseVolatileDemo
{
    /**
     * 使用:当读远多于写,结合使用内部锁和 volatile 变量来减少同步的开销
     * 理由:利用volatile保证读取操作的可见性;利用synchronized保证复合操作的原子性
     */
    public class Counter
    {
        private volatile int value;

        public int getValue()
        {
            return value;   //利用volatile保证读取操作的可见性
        }
        public synchronized int increment()
        {
            return value++; //利用synchronized保证复合操作的原子性
   	    }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值