Synchronized 关键字学习

news/2024/5/17 17:47:17 标签: 网易云课堂-微专业Java, synchronized

对初学者来讲,在多线程环境下编程,为了保证线程安全我们想到最直接办法是给代码加锁。使用synchronized关键字修饰一段代码。

我们给出最常见的demo,创建100个线程对全局变量count进行自增操作。

 int count = 0;

    public void addOperator(){
        for(int i= 0;i<100;i++ ){
             new Thread(){
                @Override
                public void run() {
                    count++;
                }
            }.start();
        }
    }

    @Test
    public void testAddOperator(){
        addOperator();
        System.out.print(count);
    }

我们执行单元测试,查看结果发现有时输出100有时输出99.这是什么情况,为什么同样的代码每次的执行的结果却不同。

这引出java 内存模型(java  memory model),线程如何协同执行的。

当创建一个线程时,jvm 会开辟一块内存区域存放他。 这块内存区域又被划分成5个不同数据区域:java栈、本地方法栈、方法区、堆、程序计数器.

 

整体上。分为三部分:栈,堆,程序计数器,他们每一部分有其各自的用途;虚拟机栈保存着每一条线程的执行程序调用堆栈;堆保存着类对象、数组的具体信息;程序计数器保存着每一条线程下一次执行指令位置。这三块区域中栈和程序计数器是线程私有的。也就是说每一个线程拥有其独立的栈和程序计数器。

java并发内存模型以及内存操作规则

java内存模型中规定了所有变量都存贮到主内存(如虚拟机物理内存中的一部分)中。每一个线程都有一个自己的工作内存(如cpu中的高速缓存)。线程中的工作内存保存了该线程使用到的变量的主内存的副本拷贝。线程对变量的所有操作(读取、赋值等)必须在该线程的工作内存中进行。不同线程之间无法直接访问对方工作内存中变量。线程间变量的值传递均需要通过主内存来完成。

关于主内存与工作内存之间的交互协议,即一个变量如何从主内存拷贝到工作内存。如何从工作内存同步到主内存中的实现细节。java内存模型定义了8种操作来完成。这8种操作每一种都是原子操作。8种操作如下:

  • lock(锁定):作用于主内存,它把一个变量标记为一条线程独占状态;
  • unlock(解锁):作用于主内存,它将一个处于锁定状态的变量释放出来,释放后的变量才能够被其他线程锁定;
  • read(读取):作用于主内存,它把变量值从主内存传送到线程的工作内存中,以便随后的load动作使用;
  • load(载入):作用于工作内存,它把read操作的值放入工作内存中的变量副本中;
  • use(使用):作用于工作内存,它把工作内存中的值传递给执行引擎,每当虚拟机遇到一个需要使用这个变量的指令时候,将会执行这个动作;
  • assign(赋值):作用于工作内存,它把从执行引擎获取的值赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的指令时候,执行该操作;
  • store(存储):作用于工作内存,它把工作内存中的一个变量传送给主内存中,以备随后的write操作使用;
  • write(写入):作用于主内存,它把store传送值放到主内存中的变量中。

我的理解是线程操作是线程读取(read)主线程的变量,加载(load)到线程栈中。使用(use)这个局部变量并赋值(assign)。再存储(store),线程栈同步到主线程。

多线程操作存在线程读取主线程的变量并不是最新的情况。线程读取主线程的变量后,而这个变量却被其他的线程修改。

锁的释放和获取的内存语义

  锁是Java并发编程中最重要的同步机制。锁除了让临界区互斥执行外,还可以让释放锁的
线程向获取同一个锁的线程发送消息。当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的 临界区代码必须从主内存中读取共享变量 

  对比锁释放-获取的内存语义与volatile写-读的内存语义可以看出:锁释放与volatile写有 相同的内存语义;锁获取与volatile读有相同的内存语义。

Synchronized 实现原理

synchronized是基于Monitor来实现同步的。

Monitor从两个方面来支持线程之间的同步:

  • 互斥执行

  • 协作

1、Java 使用对象锁 ( 使用 synchronized 获得对象锁 ) 保证工作在共享的数据集上的线程互斥执行。

2、使用 notify/notifyAll/wait 方法来协同不同线程之间的工作。

3、Class和Object都关联了一个Monitor。

继续深入学习前,了解两个重要概念。

Java对象头、monitor

Java对象头和monitor是实现synchronized的基础。

synchronized 用的锁存在java对象头里。Hotspot 虚拟机的对象头主要包括两部分数据 标记字段 mark word , 类型指针klass pointer。  klass pointer 是描述对象指向它的类元数据指针。虚拟机通过这个指针确认这个对象是哪个类的实例。

mark word 记录对象的运行时数据。它是实现轻量级锁和偏向锁关键。

mark word 记录对象自身的运行时数据, 比如hashCode GC分代年龄锁状态标志线程持有的锁偏向线程id偏向时间戳

java 对象头一般占用2个字节码。

 什么叫monitor, 一种同步工具。

与一切皆对象一样,所有的java对象都是天生的monitor,

在java设计有看不见的锁,monitor锁。

Monitor 是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表。

每一个被锁住的对象都会和一个monitor关联(对象头的MarkWord中的LockWord指向monitor的起始地址),同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。其结构如下:

 

Owner:初始时为NULL表示当前没有任何线程拥有该monitor record,当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL;

EntryQ:关联一个系统互斥锁(semaphore),阻塞所有试图锁住monitor record失败的线程。

RcThis:表示blocked或waiting在该monitor record上的所有线程的个数。

Nest:用来实现重入锁的计数。

HashCode:保存从对象头拷贝过来的HashCode值(可能还包含GC age)。

Candidate:用来避免不必要的阻塞或等待线程唤醒,因为每一次只有一个线程能够成功拥有锁,如果每次前一个释放锁的线程唤醒所有正在阻塞或等待的线程,会引起不必要的上下文切换(从阻塞到就绪然后因为竞争锁失败又被阻塞)从而导致性能严重下降。Candidate只有两种可能的值0表示没有需要唤醒的线程1表示要唤醒一个继任线程来竞争锁。

 

Java中的锁[原理、锁优化、CAS、AQS]

Monitor 工作原理

  • 当一个线程进入同步代码,为了继续执行临界代码,必须争抢Monitor锁,如果获得锁成功,则成为该对象监视器使用者。对象的监视器只属于一个活动线程。Monitor对象的Owner设置成线程标志。
  • 拥有监视器对象的线程,可以调用wait进入waitSet。释放当前Monitor对象锁Owner, 进入等待状态。 
  • 其他线程调用该对象的notify/notifyAll方法,可以释放waitSet集合的线程。
  • 同步代码执行完毕后,线程退出临界区,并释放监视锁。Monitor 对象Owner 设置为null。

synchonzied 具体实现有

 

 

锁优化

jdk1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。

自旋锁:

 


http://www.niftyadmin.cn/n/1281667.html

相关文章

快速学习-JUC

JUC简介 在 Java 5.0 提供了 java.util.concurrent &#xff08;简称JUC &#xff09;包&#xff0c;在此包中增加了在并发编程中很常用的实用工具类&#xff0c;用于定义类似于线程的自定义子系统&#xff0c;包括线程池、异步 IO 和轻量级任务框架。提供可调的、灵活的线程池…

Springboot 自动装配@EnableAutoConfiguration 学习

Springboot 能自动配置。零配置功能由EnableAutoConfiguration 实现的。 学习EnableAutoConfiguration 注解。 整个链路是 EnableAutoConfiguration >> AutoConfigurationImportSelector >> SpringFactoriesLoader >> META-INF/spring.factories /…

《PhoneApplicationPage》应用程序栏

一、 1、应用程序栏中图标大小为48*48像素。 2、图标周围的圆圈是由Windows Phone添加的&#xff08;所以不应在您的图标中包含它&#xff09;。 3、为了避免盖住圆圈&#xff0c;图标的内容应该出现在图标中心处26*26像素的正方形内。 4、图标应该具有透明的背景并且实际图标应…

jdk1.6对 sychronized 关键字优化

锁消除 一个线程内部不断的申请加锁解锁&#xff0c;却没有竞争。达到一定次数&#xff0c;经过JIT编译&#xff0c;编译器发现没有sychronized 关键字并没有真正抢锁。就会进行锁消除。 Lock 接口是没有的。 锁粗化 减少不必要的lock unLock() 将多个连续的锁扩展成一个范围更…

快速学习-Azkaban概述

一 概述 1.1 什么是 Azkaban Azkaban 是由 Linkedin 公司推出的一个批量工作流任务调度器&#xff0c;主要用于在一个工作流内以一个特定的顺序运行一组工作和流程&#xff0c;它的配置是通过简单的 key:value 对的方式&#xff0c;通过配置中的 Dependencies 来设置依赖关系…

synchronized 轻量级锁原理学习

理解java 对象头 在JVM中&#xff0c;对象存活在堆中。对象包含2块数据。 对象对象头实例变量padding&#xff08;小8个字节&#xff0c;仅做补齐用&#xff09; 而这个对象头包含3块数据。 对象头mark wordclass meta address 指向方法区 表示这具体什么类array length 如果…

jdk 1.6 synchronized 偏向锁

Hotspot的作者经过研究发现&#xff0c;大多数情况下&#xff0c;锁不仅不存在多线程竞争&#xff0c;而且总是由同一线程多次获得&#xff0c;为了让线程获得锁的代价更低而引入了偏向锁。偏向锁的意思是如果一个线程获得了一个偏向锁&#xff0c;如果在接下来的一段时间中没有…

Mac HomeBrew安装慢解决方案(转)

网速尚可&#xff0c;可使用此方法官网安装Homebrew /usr/bin/ruby -e “$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)”网速慢处理方案 先获取install文件 curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/ins…