`
maosheng
  • 浏览: 550312 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Java 并发编程_Synchronized

    博客分类:
  • Java
 
阅读更多
硬件的效率和一致性:

由于计算机的运算速度和它的存储和通讯子系统的速度差距巨大,大部分时间都花在IO,网络和数据库上。为了压榨CPU的运算能力,需要并发。

由于运算速度的差距,CPU和存储设备间加入多层的cache。同时也引入了缓存一致性的问题。解决缓存一致性有多种读写协议,(MSI,MESI,MOSI,Synapse,Firefly和Dragon Protocol等。




JAVA内存模型:

主内存和工作内存
1.所有的变量都存储在主内存中
2.每个线程都还有自己的工作内存,拥有主内存的对象的拷贝
3.线程只能操作自己的工作内存,线程间的交互只能通过主内存通讯





内存间的交互操作:

8种操作的作用场景:

1.read 和load ,store和write 必须一对操作
2.不允许线程丢弃assign操作,变量在工作内存中改变后必须把变法同步回主内存
3.没有assign操作,不允许变量从工作内存同步回主内存
4.新变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化的变量(load或assign)对一个变量实时use和store之前必须先执行过assign和load操作
5.一个变量同一时刻值允许一条线程lock操作,但lock操作可以多次,执行相同数量的unlock,变量才会解锁
6.lock操作会清空工作内存副本,执行引擎使用前,需要执行load或者assign操作初始化变量的值
7.没有lock操作,就不允许unlock操作。不允许unlock另一个线程变量。
8.unlock操作前必须先store,write操作


锁是 java 并发编程中最重要的同步机制

Java 中的每一个对象都可以作为锁。

    对于普通同步方法,锁是当前实例对象。
    对于静态同步方法,锁是当前类的 Class 对象。
    对于同步方法块,锁是 Synchonized 括号里配置的对象。

当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。

public class Account {

   private int balance;

   public Account(int balance) {
     this.balance = balance;
   }

   public int getBalance() {
     return balance;
   }

   public synchronized void add(int num) {
     balance = balance + num;
   }

   public synchronized void withdraw(int num) {
     balance = balance - num;
   }

}


每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列,就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程,当一个被线程被唤醒(notify)后,才会进入到就绪队列,等待cpu的调度。当一开始线程a第一次执行account.add方法时,jvm会检查锁对象account的就绪队列是否已经有线程在等待,如果有则表明account的锁已经被占用了,由于是第一次运行,account的就绪队列为空,所以线程a获得了锁,执行account.add方法。如果恰好在这个时候,线程b要执行account.withdraw方法,因为线程a已经获得了锁还没有释放,所以线程b要进入account的就绪队列,等到得到锁后才可以执行。

一个线程执行临界区代码过程如下:
1 获得同步锁
2 清空工作内存
3 从主存拷贝变量副本到工作内存
4 对这些变量计算
5 将变量从工作内存写回到主存
6 释放锁

可见,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。

同步的原理:

JVM 规范规定 JVM 基于进入和退出 Monitor 对象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用 monitorenter 和 monitorexit 指令实现,而方法同步是使用另外一种方式实现的,细节在 JVM 规范里并没有详细说明,但是方法的同步同样可以使用这两个指令来实现。monitorenter 指令是在编译后插入到同步代码块的开始位置,而 monitorexit 是插入到方法结束处和异常处, JVM 要保证每个monitorenter 必须有对应的 monitorexit 与之配对。任何对象都有一个 monitor 与之关联,当且一个monitor 被持有后,它将处于锁定状态。线程执行到 monitorenter指令时,将会尝试获取对象所对应的 monitor 的所有权,即尝试获得对象的锁。


Java 对象头:

Java 对象头里的 Mark Word 里默认存储对象的 HashCode,分代年龄和锁标记位。





32 位 JVM的 Mark Word 的默认存储结构:





原子操作:

处理器实现原子操作:

32 位 IA-32 处理器使用 基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。

第一个机制是通过总线锁保证原子性,总线锁就是使用处理器提供的一个 LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器可以独占使用共享内存。

第二个机制是通过缓存锁定保证原子性,在同一时刻我们只需保证对某个内存地址的操作是原子性即可,但总线锁定把 CPU 和内存之间通信锁住了,这使得锁定期间,其他处理器不能操作其他内存地址的数据,所以总线锁定的开销比较大,最近的处理器在某些场合下使用缓存锁定代替总线锁定来进行优化。频繁使用的内存会缓存在处理器的 L1,L2 和 L3 高速缓存里,那么原子操作就可以直接在处理器内部缓存中进行,并不需要声明总线锁,缓存锁定就是如果缓存在处理器缓存行中内存区域在 LOCK 操作期间被锁定,当它执行锁操作回写内存时,处理器不在总线上声言 LOCK#信号,而是修改内部的内存地址,并允许它的缓存一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时修改被两个以上处理器缓存的内存区域数据,当其他处理器回写已被锁定的缓存行的数据时会起缓存行无效。

Java 实现原子操作:

Java 中可以通过 锁和环循环 CAS 的方式来实现原子操作。


Java语言中,如果共享数据是一个基本数据类型或对象类型,那么只要在定义时使用final关键字修饰他就可以保证他是不可变的。

线程安全的实现方法:

1.互斥同步

互斥同步是最常见的一种并发正确性保障手段,同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一条线程使用。

在Java里面,最基本的互斥同步手段就是synchronized关键字,synchronized关键字经过编译之后,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令,这两个字节码都需要一个reference类型的参数来指明要锁定和解锁的对象。如果Java程序中的synchronized明确指定了对象参数,那就是这个对象的reference;如果没有明确指定,那就根据synchronized修饰的是实例方法还是类方法,去取对应的对象实例或Class对象来作为锁对象。

根据虚拟机规范的要求,在执行monitorentera指令时,首先要去尝试获取对象的锁。如果这个对象没被锁定,或者当前线程已经拥有了那个对象的锁,把锁的计数器加1,相应地,在执行monitorexit指令时会将锁计数器减1,当计数器为0时,锁就被释放了。如果获取对象锁失败了,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。

Java的线程是映射到操作系统的原生线程之上的,如果要阻塞或唤醒一条线程,都需要操作系统来帮忙完成,这就需要从用户态转换到核心态中,因此转态转换需要耗费很多的处理器时间。对于diamante简单的同步块(getter()或setter()方法),状态转换消耗的时间可能比用户代码执行的时间还要长。所以synchronized是java语言中一个重量级的操作。

2.非阻塞同步

互斥同步最主要的问题就是进行线程阻塞或唤醒所带来的性能问题,因此这种同步也被称为阻塞同步。另外,它属于一种悲观的并发策略,总是认为只要不去做正确的同步措施(加锁),那就肯定出现问题,无论共享数据是否真的会出现竞争,它都要进行加锁、用户态和心态转换、维护锁计数器和检查是否有被阻塞的线程需要被唤醒等操作。随着硬件指令集的发展(因为我们需要操作和冲突检测这两个步骤具备原子性),我们有了另外一个选择:基于冲突检测的乐观并发策略,通俗地说就是先进行操作,如果没有其他线程争用共享数据,那操作就成功了,如果共享数据有争用,产生了冲突,那就再进行其他的补偿措施(最常见的补偿措施就是不断地重试,直到试成功为止),这个乐观的并发策略的许多实现都不需要把线程挂起,因此这种同步操作被称为非阻塞同步。

3.无同步方案

要保证线程安全,并不是一定就要进行同步,两者没有因果关系。同步只是保障共享数据争用时的正确性的手段,如果一个方法本来就不涉及共享数据,那它自然就无须任何同步措施去保证正确性,因此会有一些代码天生就是线程安全的。

1)可重入代码(Reentrant Code):这种代码也叫纯代码,可以在代码执行的任何时刻中断它,转而去执行另外一段代码,而在控制权返回后,原来的程序不会出现任何错误。相对线程安全来说,可重入性更基本的特性,它可以保证线程安全,即所有的可重入的代码都是线程安全的,但是并非所有的线程的代码都是可重入的。

可重入代码有一些共同的特征:例如不依赖存储在堆上的数据和公用的系统资源、用的的状态都由参数中传入、不调用非可重入的方法等。如果一个方法,他的返回结果是可以预测的,只要输入了相同的数据,就都能返回相同的结构,那它就满足可重入性的要求,当然也就是线程安全的。

2)线程本地存储(Thread Local Storage):如果一段代码中所需要的数据必须与其他代码共享,那就看看这些共享数据的代码是否能保证在同一个线程中执行?如果能保证,我们就可以把共享数据的可见范围限制在同一个线程之内,这样,无须同步也能保证线程之间不出现数据争用的问题。Java语言中,如果一个变量要被多线程访问,可以使用volatile关键字声明它为“易变的”,如果一个变量要被某个线程独享,可以通过java.lang.ThreadLocal类来思考线程本地存储的功能。每个线程的Thread对象中都有一个TheadLocalMap对象,这个对象存储一组以ThreadLocal.threadLoaclHashCode为键,以本地线程变量为值的K-V值对,ThreadLocal对象就是当前线程的ThreadLocalMap的访问入口,每一个ThreadLocal对象都包含了一个独一无二的threaLoaclHashCode值,使用这个值就可以在线程K-V值对中找回对应的本地线程变量。










  • 大小: 29 KB
  • 大小: 16.2 KB
  • 大小: 33.7 KB
  • 大小: 55.8 KB
分享到:
评论

相关推荐

    Java并发编程系列- synchronized

    Java并发编程系列- synchronized;Java并发编程系列- synchronized;Java并发编程系列- synchronized;

    【Java并发编程】Synchronized关键字实现原理.doc

    【Java并发编程】Synchronized关键字实现原理.doc

    Java 并发核心编程

    本文的主题是关于具有java语言风格的Thread、synchronized、volatile,以及J2SE5中新增的概念,如锁(Lock)、原子性(Atomics)、并发集合类、线程协作摘要、Executors。开发者通过这些基础的接口可以构建高并发、线程...

    Java并发编程---synchronized关键字

    Java并发编程---synchronized关键

    java高级技术JUC高并发编程教程2021(1.5G)

    java高级技术JUC高并发编程教程2021(1.5G) 〖课程介绍〗: java高级技术JUC高并发编程教程2021(1.5G) 〖课程目录〗:   01-JUC高并发编程-课程介绍.mp4 02-JUC高并发编程-JUC概述和进程线程概念(1).mp4 03-JUC...

    java并发编程

    本书全面介绍了如何使用Java 2平台进行并发编程,较上一版新增和扩展的内容包括:, ·存储模型 ·取消 ·可移植的并行编程 ·实现并发控制的工具类, Java平台提供了一套广泛而功能强大的api,工具和技术。...

    Java并发编程:设计原则与模式(第二版)

    java并发方面的两大名著之一。读者将通过使用java.lang.thread类、synchronized和volatile关键字,以及wait、notify和notifyall方法,学习如何初始化、控制和协调并发操作。此外,本书还提供了有关并发编程的全方位...

    深入java并发编程,使用ReentrantLock和 Synchronized加锁

    深入java并发编程,使用ReentrantLock和 Synchronized加锁

    Java并发编程学习笔记

    7、并发工具类CountDownLatch 、CyclicBarrier和Semaphore底层实现原理 8、线程池原理和如何使用线程池 9、ThreadLocal 为什么会内存泄漏 10、Volatile底层实现原理 11、AQS源码分析 12、CAS原理分析和使用场景 13、...

    Java并发编程原理与实战

    Spring对并发的支持:Spring的异步任务.mp4 使用jdk8提供的lambda进行并行计算.mp4 了解多线程所带来的安全风险.mp4 从线程的优先级看饥饿问题.mp4 从Java字节码的角度看线程安全性问题.mp4 synchronized保证线程...

    龙果 java并发编程原理实战

    龙果 java并发编程原理实战 第2节理解多线程与并发的之间的联系与区别 [免费观看] 00:11:59分钟 | 第3节解析多线程与多进程的联系以及上下文切换所导致资源浪费问题 [免费观看] 00:13:03分钟 | 第4节学习并发的四...

    java并发编程面试题

    java并发编程 基础知识,守护线程与线程, 并行和并发有什么区别? 什么是上下文切换? 线程和进程区别 什么是线程和进程? 创建线程有哪几种方式?,如何避免线程死锁 线程的 run()和 start()有什么区别? 什么是 ...

    java并发编程理论基础精讲

    本资源为您提供了关于 Java 并发编程理论基础的精讲,涵盖了多线程编程的核心概念、基本原理以及在 Java 中的应用。通过深入学习,您将建立坚实的并发编程基础,能够更好地理解和应对多线程编程中的挑战。 并发编程...

    Java并发编程基础.pdf

    Java并发编程基础主要包括以下几个核心方面: 线程与线程状态:理解Java中线程的基本概念,包括线程的创建、启动、暂停、恢复和终止。熟悉线程的生命周期及其不同状态,如新建、就绪、运行、阻塞和死亡。 线程同步...

    java多线程编程_java多线程_

    4.讲解了synchronized关键字 , 它使用起来比较麻烦, 所以在Java5中提供了Lock 对象 , 以求能更好地实现并发访问时的同步处理, 包括读写锁等相关技术点。5.讲解了Timer定时器类, 其内部实现就是使用的多线程技术...

    Java 7并发编程实战手册

    java7在并发编程方面,带来了很多令人激动的新功能,这将使你的应用程序具备更好的并行任务性能。 《Java 7并发编程实战手册》是Java 7并发编程的实战指南,介绍了Java 7并发API中大部分重要而有用的机制。全书分为9...

    龙果java并发编程完整视频

    第1节你真的了解并发吗? [免费观看][免费观看] 00:27:48分钟 | 第2节理解多线程与并发的之间的联系与区别 [免费观看] 00:11:59分钟 | 第3节解析多线程与多进程的联系以及上下文切换所导致资源浪费问题 [免费观看]...

    Java并发编程相关技术使用案例

    1、本资源包含并发编程基础知识的使用案例,包括:线程创建、Synchronized和Reentrantlock锁的使用、线程安全问题演示、Condition的应用、CountDownLatch的应用、Cyclicbarrier的应用、Semaphore的应用、线程池的...

    汪文君高并发编程实战视频资源下载.txt

    │ Java并发编程.png │ ppt+源码.rar │ 高并发编程第二阶段01讲、课程大纲及主要内容介绍.wmv │ 高并发编程第二阶段02讲、介绍四种Singleton方式的优缺点在多线程情况下.wmv │ 高并发编程第二阶段03讲、...

Global site tag (gtag.js) - Google Analytics