带你了解synchronized关键字

导航

内容导图

关于线程安全的那些事儿

锁原来有这么多分类

synchronized的作用域

锁的优化

锁的原理


我们在学习Java多线程时一定听说过synchronized关键子。而本文就会带你了解synchronized关键字中暗藏的玄机。

整个学习内容如下图,咱们接着往下看。

内容导图


关于线程安全的那些事儿

要完全理解synchronized,咱们就要先知道synchronized的出现是为了什么——解决线程安全问题。

首先出现线程安全问题的原因主要由两方面构成,可见性与原子性。在这里不详细展开,只是大致的解释一下相关问题。

可见性:在JMM(Java内存模型)中所有变量都存储在主内存中,而每个线程都有自己独立的工作内存,里面保存该线程的使用到的变量副本(该副本就是主内存中该变量的一份拷贝)。线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接在主内存中读。不同线程之间无法直接访问其他线程工作内存中的变量,这就导致线程间变量值的传递需要通过主内存来完成。而这一传递过程是需要耗时的

那么问题来了。当一个线程对共享变量的修改时,而其他线程不能够及时的看到,这时候就产生了可见性问题。

原子性:是指一个操作是不可中断的. 即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干扰。这里可以理解为是一个CAS操作,至于什么是CAS操作,这里不做具体讲解。我在之后的博客中会单独说明。

那么问题来了,在程序执行过程中, 为了性能考虑, 编译器和CPU可能会对指令重新排序。那么在源码(.java)中看上去像是原子性操作的代码,经过编译器编译后的字节码(.class)指令可能是由多个步骤组成。既然指令可以被分解为很多步骤, 那么多条指令就不一定依次序执行(会有其他线程抢先执行)。这时候就产生了原子性问题。


锁原来有这么多分类

在Java锁中锁可以分为如下几类,请注意,一下的分类不是同一纬度的,而是从不同纬度进行的分类,也就是说,一个锁它可以是悲观锁,同时也可以是可重入锁。其实synchronized就是一个可重入锁,独享锁,悲观锁

  • 自旋锁:是指当一个线程在获取锁当时候,如果锁已经被其他线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
  • 乐观锁:假定没有冲突,在修改数据时如果发现数据和之前获取的不一致,则读最新数据,修改后重试修改
  • 悲观锁:假定会发生冲突,同步所有对数据的相关操作,从读数据就开始上锁。
  • 独享锁(写):给资源加上写锁,线程可以修改资源,其他线程不能再加锁;(单写)
  • 共享锁(读):给资源加上读锁后只能读不能改,其他线程也只能读锁,不能加写锁;(多读)
  • 可重入、不可重入锁:线程拿到一把锁之后,可以自由进入同一把锁锁同步的其他代码。
  • 公平锁、非公平锁:争抢锁的顺序,如果是按先来后到,则为公平。

这里咱们看下可重入锁。下面这段代码使用递归执行,控制台打印结果不会阻塞,也就是说,虽然已经上了锁,但是再进入这段被锁的代码后,只要是同一把锁,还是会执行下去。也就是说会不停的每隔2s打印一次“This i is [i]”

package com.xavier.common;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author : xavier
 * @version : 1.0
 */
public class ReentrantTest {

    private static int i = 0;

    private final static Lock LOCK = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        test();
    }

    private static void test() throws InterruptedException {
        LOCK.lock();
        i++;
        System.out.println("This i is " + i);
        Thread.sleep(2000L);
        test();
        LOCK.unlock();
    }
}

synchronized的作用域

请看下面这段代码,在使用synchronized关键字时 add() 与 add1() 锁定的是对象,而 addStatic() 与 add2() 锁定的是类对象。

add() 与 addStatic() 是隐式指定锁对象的,add1() 与 add2() 则是显示的指定锁对象的。

package com.xavier.common;
/**
 * @author : xavier
 * @version : 1.0
 */
public class Sync {

    public static void main(String[] args) {
        Sync sync1 = new Sync();
        Sync sync2 = new Sync();
        sync1.add();
        sync2.add();
    }

    private static int i;

    /**
     * 对象锁
     */
    public synchronized void add() {
        i++;
    }

    /**
     * 类锁
     */
    public synchronized static void addStatic() {
        i++;
    }

    /**
     * 对象锁,锁住的是当前的对象,当创建多个对象的时候,会有多个锁,起不到互斥的作用,锁会失效。
     */
    public void add1() {
        synchronized (this) {

        }
    }

    /**
     * 类锁,类对象在方法区。不会存在对象锁的问题。
     */
    public static void add2() {
        synchronized (Sync.class) {

        }
    }
}

锁的优化

锁消除:在JVM中如果发现当前锁始终只有一个线程在调用,不存在竞争关系,那么它就会将synchronized优化,即消除锁。注意:Lock接口中的Reenterlock并不会优化。

锁粗化:请看下面这段代码,不要吐槽写的烂(我就是故意这样写的:))

package com.xavier.common;
/**
 * @author : xavier
 * @version : 1.0
 */
public class Test {
    
    int i = 0;
    
    public void test() {
        
        synchronized (this) {
            i++;
        }
        
        synchronized (this) {
            i--;
        }

        System.out.println("This is a test");
        
        synchronized (this) {
            System.out.println("This is a test");
        }
        
        synchronized (this) {
            i++;
        }
    }
}

上面这段代码会被JIT即时编译优化为下面。注意:如果是耗时操作,就不会这样优化了。长时间的持有锁,也是消耗性能的。

package com.xavier.common;
/**
 * @author : xavier
 * @version : 1.0
 */
public class Test {

    int i = 0;

    public void test() {

        synchronized (this) {
            i++;
            i--;
            System.out.println("This is a test");
            System.out.println("This is a test");
            i++;
        }
    }
}

这就是锁的粗化,是不是瞬间就懂了呢。

偏向锁:偏向锁主要目的是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径。应为哪怕是轻量级锁也需要CAS操作和自旋操作。在JDK6以后,默认已经开启了偏向锁这个优化,通过JVM参数-XX:-UseBiasedLocking来禁用偏向锁。若偏向锁开启,只有一个线程抢锁,可获取到偏向锁,只有一个线程的时候就会使用到偏向锁。

偏向锁是不会释放的,也就是说 thread id不会置为零

轻量级锁:轻量级锁的主要目的是在多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。当关闭偏向锁功能或者多个线程竞争偏向锁导致偏向锁升级为轻量级锁,则会尝试获取轻量级锁


锁的原理

在了解偏向锁与轻量级锁之前,咱们先看一下下面这张图。

图中左边是Java对象在堆内存中的简要模型包括对象头,实例数据引用和对齐填充,其中,对象头Object Head中包含了图中右边的相关信息,而咱们要知道的就是粉红色标记的Mark Word

让我们看看Mark Word的owner里面包含了那些信息吧。如下图,这是详细的对象锁状态在Mark Word的owner的信息。

BitfieldsTagstate 
HashcodeAge001Unlocked未锁定
Thread IDAge101Biased/ biased偏向锁
Lock record address00

Light-weight locked

轻量级锁
Monitor address10Heavy-weight locked重量级锁
Forwarding address, etc.11Marked for GC 

线程会在虚拟机栈帧中开辟一块内存空间,存放拷贝的Lock Record 其中包括 Hashcode Age 0,当要抢锁的时候,会进行CAS操作将Lock record address 修改, 其中owner指向当前的对象头的mark word。当线程抢锁失败后就会进入自旋,达到一定次数就会进行锁升级,将轻量级锁升级为重量级锁。注意:如果线程复制不到Lock Record则会直接升级成重量级锁。

重量级锁原理如下

如图:

synchronized的对象锁,其指针指向的是一个monitor对象(由C++实现)的起始地址。每个对象实例都会有一个 monitor。其中monitor可以与对象一起创建、销毁;亦或者当线程试图获取对象锁时自动生成。

在对象监视器 Monitor 中,当线程抢占锁成功后,就会将 Lock record address 中的 00 修改为 10 并将 Lock record address修改为Monitor address。

在Monitor中 保存有owner信息(t1,t2,t3代表线程),可以理解为当前线程的引用信息。

当其他线程抢锁的自旋次数超过临界值的时候,Monitor就会将该线程放入锁池(entryList)中此时(先近先出),线程出现blocked状态

hotspot源码中可查看以上内容 ObjectMontior文件中

只用在抢到锁的时候(即owner指向的是当前线程)才能调用wait方法,当调用wait方法的时候,线程将锁释放掉(owner = null),线程进入到waitSet(等待池),线程状态出现 waiting。

这里需要注意,虽然entryList先进先出,但是也不能保证是公平的,因为其他线程可以随时抢锁,即在owner被释放掉的那一刻,在锁池中的线程需要和未进入锁池的线程同时抢锁。

而当线程唤醒的时候(从waiting状态变为runnable状态),会先从等待池出来抢占owner,如果抢占失败,则进入锁池,线程呈现blocked状态。

代码执行完就会执行montiorExit解锁

锁的升级过程

如下图展示

如果其他线程过来抢锁,锁未被占用的情况下会关闭偏向锁,然后将未锁定的已经关闭的偏向锁升级成轻量级锁,此时保存的是

Lock record address。

当升级成为轻量级的锁被释放,就会回到未锁定,关闭偏向锁的状态。

也就是说,一旦偏向锁 升级了,就不会退回到偏向锁的状态 了 

当其他线程自选争抢 轻量级锁到临界点时,就会升级成 重量级锁,这时候mark word中的Bitfields 将由原来的Lock record address 替换成 Monitor addres

需要注意的是,由于锁升级是不可逆的,所以当升级成重量级锁释放锁后,下一个线程获取到的锁任然会是重量级锁

结尾

好了,到这里基本上关于synchronized关键字的分析就告一段落了,之后我会继续补充和完善关于java锁的内容。希望大家能通过这篇文章有所收获!

智慧旅游解决方案利用云计算、物联网和移动互联网技术,通过便携终端设备,实现对旅游资源、经济、活动和旅游者信息的智能感知和发布。这种技术的应用旨在提升游客在旅游各个环节的体验,使他们能够轻松获取信息、规划行程、预订票务和安排食宿。智慧旅游平台为旅游管理部门、企业和游客提供服务,包括政策发布、行政管理、景区安全、游客流量统计分析、投诉反馈等。此外,平台还提供广告促销、库存信息、景点介绍、电子门票、社交互动等功能。 智慧旅游的建设规划得到了国家政策的支持,如《国家中长期科技发展规划纲要》和国务院的《关于加快发展旅游业的意见》,这些政策强调了旅游信息服务平台的建设和信息化服务的重要性。随着技术的成熟和政策环境的优化,智慧旅游的时机已经到来。 智慧旅游平台采用SaaS、PaaS和IaaS等云服务模式,提供简化的软件开发、测试和部署环境,实现资源的按需配置和快速部署。这些服务模式支持旅游企业、消费者和管理部门开发高性能、高可扩展的应用服务。平台还整合了旅游信息资源,提供了丰富的旅游产品创意平台和统一的旅游综合信息库。 智慧旅游融合应用面向游客和景区景点主管机构,提供无线城市门户、智能导游、智能门票及优惠券、景区综合安防、车辆及停车场管理等服务。这些应用通过物联网和云计算技术,实现了旅游服务的智能化、个性化和协同化,提高了旅游服务的自由度和信息共享的动态性。 智慧旅游的发展标志着旅游信息化建设的智能化和应用多样化趋势,多种技术和应用交叉渗透至旅游行业的各个方面,预示着全面的智慧旅游时代已经到来。智慧旅游不仅提升了游客的旅游体验,也为旅游管理和服务提供了高效的技术支持。
智慧旅游解决方案利用云计算、物联网和移动互联网技术,通过便携终端设备,实现对旅游资源、经济、活动和旅游者信息的智能感知和发布。这种技术的应用旨在提升游客在旅游各个环节的体验,使他们能够轻松获取信息、规划行程、预订票务和安排食宿。智慧旅游平台为旅游管理部门、企业和游客提供服务,包括政策发布、行政管理、景区安全、游客流量统计分析、投诉反馈等。此外,平台还提供广告促销、库存信息、景点介绍、电子门票、社交互动等功能。 智慧旅游的建设规划得到了国家政策的支持,如《国家中长期科技发展规划纲要》和国务院的《关于加快发展旅游业的意见》,这些政策强调了旅游信息服务平台的建设和信息化服务的重要性。随着技术的成熟和政策环境的优化,智慧旅游的时机已经到来。 智慧旅游平台采用SaaS、PaaS和IaaS等云服务模式,提供简化的软件开发、测试和部署环境,实现资源的按需配置和快速部署。这些服务模式支持旅游企业、消费者和管理部门开发高性能、高可扩展的应用服务。平台还整合了旅游信息资源,提供了丰富的旅游产品创意平台和统一的旅游综合信息库。 智慧旅游融合应用面向游客和景区景点主管机构,提供无线城市门户、智能导游、智能门票及优惠券、景区综合安防、车辆及停车场管理等服务。这些应用通过物联网和云计算技术,实现了旅游服务的智能化、个性化和协同化,提高了旅游服务的自由度和信息共享的动态性。 智慧旅游的发展标志着旅游信息化建设的智能化和应用多样化趋势,多种技术和应用交叉渗透至旅游行业的各个方面,预示着全面的智慧旅游时代已经到来。智慧旅游不仅提升了游客的旅游体验,也为旅游管理和服务提供了高效的技术支持。
深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值