synchronized和LOCK的实现原理---深入JVM锁机制--比较好
synchronized和lock底层原理
synchronized和lock底层原理
synchronized和lock都是Java中用于实现线程同步的关键字和类,它们的实现原理都涉及到Java虚拟机的底层机制。
synchronized是Java中最基本的同步机制之一,它实现了Java 中的内置锁机制。
当一个线程访问一个互斥区域时,synchronized
会自动将该区域上锁,保证同一时刻只有一个线程可以访问该区域。
在synchronized的实现过程中,Java虚拟机会使用对象头上的标志位记录锁的状态,同时使用monitorenter和monitorexit指令实现对锁的获取和释放。
而lock是Java中提供的另一种同步机制,它是通过Java中的ReentrantLock类实现的。
与synchronized不同的是,lock是一种显式锁,需要手动获取和释放。
在lock的实现过程中,Java虚拟机会使用CAS(Compare And Swap)指令来保证对锁的获取和释放的原子性,从而避免了出现死锁和饥饿等问题。
总的来说,synchronized和lock都是Java中用于实现线程同步的关键字和类,它们的底层实现机制都涉及到Java虚拟机的底层机制。
在使用时需要根据实际情况选择合适的同步机制,以保证程序的正确性和效率。
- 1 -。
Java锁机制详解:synchronized关键字与Lock接口
Java锁机制详解:synchronized关键字与Lock接口在Java编程中,锁机制是实现多线程并发控制的重要手段之一。
本文将深入探讨Java中的两种常见锁机制:synchronized关键字与Lock接口,分析它们的特点、使用方法和适用场景。
synchronized关键字synchronized关键字是Java中最基本的锁机制之一,它可以应用于方法或代码块中,用来实现对共享资源的互斥访问。
当一个线程获得对象的synchronized 锁时,其他线程无法进入被锁定的代码块,直到锁被释放。
特点•隐式获取释放锁:使用synchronized关键字时,锁的获取和释放是隐式进行的,简化了锁管理的复杂性。
•非公平锁:synchronized锁是非公平的,即线程获取锁的顺序与线程启动的顺序无关。
•重量级锁:synchronized锁是JVM实现的一种重量级锁,在Java 6之前性能较差,但在之后的版本有所优化。
使用方法public synchronized void synchronizedMethod() {// 同步代码块// 访问共享资源}适用场景•简单场景:对于简单的线程同步问题,使用synchronized关键字足以满足需求。
•代码块级别控制:需要实现代码块级别的同步时,可以考虑使用synchronized关键字。
•性能要求不高:对性能要求不高的场景,synchronized关键字可以提供简单有效的线程同步方式。
Lock接口除了synchronized关键字外,Java还提供了Lock接口及其实现类,如ReentrantLock,用于实现更灵活、细粒度的锁控制。
特点•显式获取释放锁:Lock接口提供了显式的获取和释放锁的方法,可以更精细地控制锁的获取和释放时机。
•可重入锁:Lock接口的实现类通常支持可重入性,允许同一线程多次获取同一个锁。
•公平锁与非公平锁:Lock接口的实现类可以根据需求选择公平锁或非公平锁。
java两种同步机制的实现synchronized和reentrantlock
java两种同步机制的实现synchronized和reentrantlock java两种同步机制的实现 synchronized和reentrantlock双11加保障过去⼀周,趁现在有空,写⼀点硬货,因为在进⼊阿⾥之后⼯作域的原因之前很多java知识点很少⽤,所以记录⼀下,以后忘了也还可以看⼀下,以及对多线程同步不擅长的同学也可以参考。
我们知道,java是⼀种⾼级语⾔,java运⾏在jvm中,java编译器会把我们程序猿写的java代码编译成.class⽂件,这个.class对于jvm就是相当于汇编对于操作系统(jvm也有类似操作系统⼀样的指令集),当jvm运⾏的时候,它会把.class翻译成操作系统认识的指令集然后运⾏在操作系统(这⾥对java是解释型还是编译型语⾔不做深究),当然除此之外,java还可以通过jni(java native interface)调⽤c++,c的代码(c++,c 还可以内嵌汇编代码,所以java是可以间接调⽤汇编的),jvm内存模型中有⼀个是Native Method Stacks,这⾥就是我们调⽤本地⽅法的栈,我们今天讲的java同步,就同时和java指令集和jni有关,下⾯进⼊正题。
当在java中使⽤多线程的时候,我们肯定要考虑到线程安全,线程安全简单说就是多个线程操作同⼀个变量不会出现结果不确定性,那为什么会出现线程不安全?那是因为java的内存模型决定,jvm有⼀个主内存,还有线程独有的线程上的⼯作内存,我打个⽐⽅,⼀个线程就是⼀个cpu,cpu有⾃⼰的多级缓存,还有⼀个内存条的内存。
下⾯就盗个图简单说明⼀下:因为这个原因,所以当多个线程修改同⼀个变量的时候,会出现不确定的结果,这就导致了线程不安全,那怎么样我们才能确保线程安全呢?说的通俗⼀点,就是排队,没错,当多个线程需要操作同⼀个变量的时候,排队⼀个⼀个来,那要怎样才能实现排队?机器不像⼈这么聪明,看到前⾯有“⼈”就站着等,那最简单粗暴的⽅法就是加锁,可以想象⼀下,⼀个线程要去修改⼀个变量的时候,前⾯有⼀道门,当门被锁住的时候,线程就只能等待或者⼲其他事。
java并发之线程同步(synchronized和锁机制)
java并发之线程同步(synchronized和锁机制)多个执⾏线程共享⼀个资源的情景,是并发编程中最常见的情景之⼀。
多个线程读或者写相同的数据等情况时可能会导致数据不⼀致。
为了解决这些问题,引⼊了临界区概念。
临界区是⼀个⽤以访问共享资源的代码块,这个代码块在同⼀时间内只允许⼀个线程执⾏。
Java提供了同步机制。
当⼀个线程试图访问⼀个临界区时,它将使⽤⼀种同步机制来查看是不是已有其他线程进⼊临界区。
如果没有其他线程进⼊临界区,它就可以进⼊临界区;如果已有线程进⼊了临界区,它就被同步机制挂起,直到进⼊的线程离开这个临界区。
如果在等待进⼊临界区的线程不⽌⼀个,JVM会随机选择其中的⼀个,其余的将继续等待。
概念⽐较好理解,具体在java程序中是如何体现的呢?临界区对应的代码是怎么样的?使⽤synchronized实现同步⽅法每⼀个⽤synchronized关键字声明的⽅法都是临界区。
在Java中,同⼀个对象的临界区,在同⼀时间只有⼀个允许被访问。
注意:⽤synchronized关键字声明的静态⽅法,同时只能被⼀个执⾏线程访问,但是其他线程可以访问这个对象的⾮静态⽅法。
即:两个线程可以同时访问⼀个对象的两个不同的synchronized⽅法,其中⼀个是静态⽅法,⼀个是⾮静态⽅法。
知道了synchronized关键字的作⽤,再来看⼀下synchronized关键字的使⽤⽅式。
在⽅法声明中加⼊synchronized关键字1public synchronized void addAmount(double amount) {2 }在代码块中使⽤synchronized关键字,obj⼀般可以使⽤this关键字表⽰本类对象1synchronized(obj){2 }需要注意的是:前⾯已经提到,引⼊synchronized关键字是为了声明临界区,解决在多线程环境下共享变量的数据更改安全问题。
那么,⼀般⽤到synchronized关键字的地⽅也就是在对共享数据访问或者修改的地⽅。
java单机锁的原理
java单机锁的原理Java中的单机锁是通过synchronized关键字或者Lock接口实现的。
下面我会从多个角度来解释Java单机锁的原理。
首先,让我们来看看synchronized关键字。
在Java中,synchronized关键字可以应用于方法或代码块。
当一个线程进入一个synchronized方法或代码块时,它会尝试获取对象的锁。
如果锁已经被另一个线程获取了,那么当前线程就会被阻塞,直到锁被释放。
一旦当前线程获取了锁,它就可以执行同步方法或代码块中的代码,其他线程则无法同时执行这些代码,直到当前线程释放了锁。
这种方式可以确保同一时刻只有一个线程可以访问被锁定的代码,从而保证了线程安全性。
其次,让我们来看看Lock接口。
Java中的Lock接口提供了比synchronized关键字更灵活的锁定机制。
Lock接口的实现类可以实现不同的锁定策略,比如可重入锁、公平锁等。
Lock接口中的lock()方法和unlock()方法分别用于获取锁和释放锁。
与synchronized关键字不同,Lock接口还提供了tryLock()方法,它可以尝试获取锁而不会导致线程阻塞,这在一些特定的场景下会更加灵活。
无论是synchronized关键字还是Lock接口,它们的实现都依赖于Java虚拟机中的监视器(monitor)机制。
每个Java对象都与一个监视器相关联,这个监视器可以被用来实现对象的同步。
当一个线程尝试获取一个对象的锁时,它实际上是在尝试获取与这个对象关联的监视器。
Java虚拟机会负责管理监视器的获取和释放,以确保多个线程之间的协调和同步。
总的来说,Java中的单机锁的原理是通过synchronized关键字或Lock接口来实现的,它们都依赖于Java虚拟机中的监视器机制来确保多线程之间的同步和互斥。
这种锁定机制可以帮助我们编写线程安全的代码,从而避免多线程并发访问时可能出现的数据竞争和不一致性。
java锁实现原理
java锁实现原理
Java中的锁是实现多线程同步的重要工具,它可以保证在同一时刻只有一个线程能够访问共享资源。
在Java中,锁的实现主要有两种方式:synchronized关键字和Lock接口。
synchronized关键字是Java中最基本的锁实现方式,它通过在方法或代码块前加上synchronized关键字,使得在同一时刻只有一个线程能够访问该方法或代码块。
synchronized关键字的实现是基于Java虚拟机中的监视器锁(monitor),在执行synchronized代码块之前,虚拟机会对该代码块所对应的监视器锁进行加锁操作,只有在该锁被释放后,其他线程才能够获取该锁并执行相应的代码块。
除了synchronized关键字,Java还提供了Lock接口来实现锁操作。
Lock接口提供了比synchronized更加灵活和细粒度的锁控制,它可以实现公平锁、可重入锁以及读写锁等特性,而synchronized 关键字只能实现非公平锁和不可重入锁。
Lock接口的实现是基于Java中的AbstractQueuedSynchronizer(AQS)类,AQS是通过一个FIFO的双向队列来实现线程的等待和唤醒的。
Java中的锁实现原理基于线程的调度机制,在多线程场景下,通过加锁和解锁操作,保证了共享资源的访问顺序和资源的正确性,从而实现了线程同步。
在实际的开发中,需要根据实际情况选择合适的锁实现方式,以达到最佳的性能和效果。
- 1 -。
深入理解java:2.2.同步锁Synchronized及其实现原理
深⼊理解java:2.2.同步锁Synchronized及其实现原理同步的基本思想为了保证共享数据在同⼀时刻只被⼀个线程使⽤,我们有⼀种很简单的实现思想,就是在共享数据⾥保存⼀个锁,当没有线程访问时,锁是空的。
当有第⼀个线程访问时,就在锁⾥保存这个线程的标识并允许这个线程访问共享数据。
在当前线程释放共享数据之前,如果再有其他线程想要访问共享数据,就要等待锁释放。
在共享数据⾥保存⼀个锁在锁⾥保存这个线程的标识其他线程访问已加锁共享数据要等待锁释放Jvm同步的实现jvm中有以下三种锁(由上到下越来越“重量级”):1. 偏向锁2. 轻量级锁3. 重量级锁重量级锁Synchronized 原理我们直接参考JVM规范中描述:每个对象有⼀个监视器锁(monitor)。
当monitor被占⽤时就会处于锁定状态,线程执⾏monitorenter指令时尝试获取monitor的所有权,过程如下:1、如果monitor的进⼊数为0,则该线程进⼊monitor,然后将进⼊数设置为1,该线程即为monitor的所有者。
2、如果线程已经占有该monitor,只是重新进⼊,则进⼊monitor的进⼊数加1.3.如果其他线程已经占⽤了monitor,则该线程进⼊阻塞状态,直到monitor的进⼊数为0,再重新尝试获取monitor的所有权。
Synchronized的语义底层是通过⼀个monitor的对象来完成,其实wait/notify等⽅法也依赖于monitor对象,这就是为什么只有在同步的块或者⽅法中才能调⽤wait/notify等⽅法,否则会抛出ng.IllegalMonitorStateException的异常的原因。
Synchronized是通过对象内部的⼀个叫做监视器锁(monitor)来实现的。
但是监视器锁本质⼜是依赖于底层的操作系统的互斥锁(Mutex Lock)来实现的。
⽽操作系统实现线程之间的切换这就需要从⽤户态转换到核⼼态,这个成本⾮常⾼,状态之间的转换需要相对⽐较长的时间,这就是为什么Synchronized效率低的原因。
java锁机制(synchronized与Lock)
java锁机制(synchronized与Lock)博客已迁移到CSDN《》 在java中,解决同步问题,很多时候都会使⽤到synchronized和Lock,这两者都是在多线程并发时候常使⽤的锁机制。
synchronized是java中的⼀个关键字,也就是说是java内置的⼀个特性。
当⼀个线程访问⼀个被synchronized修饰的代码块,会⾃动获取对应的⼀个锁,并在执⾏该代码块时,其他线程想访问这个代码块,会⼀直处于等待状态,⾃有等该线程释放锁后,其他线程进⾏资源竞争,竞争获取到锁的线程才能访问该代码块。
线程释放synchronized修饰的代码块锁的⽅式有两种:1. 该线程执⾏完对应代码块,⾃动释放锁。
2. 在执⾏该代码块是发⽣了异常,JVM会⾃动释放锁。
采⽤synchronized关键字来实现同步,会导致如果存在多个线程想执⾏该代码块,⽽当前获取到锁的线程⼜没有释放锁,可想⽽知,其他线程只有⼀只等待,这将严重印象执⾏效率。
Lock锁机制的出现就是为了解决该现象。
Lock是⼀个java接⼝,通过这个接⼝可以实现同步,使⽤Lock时,⽤户必须⼿动进⾏锁的释放,否则容易出现死锁。
ReentranLock是Lock的唯⼀实现类。
下⾯简单介绍⼀下ReentranLock与synchronized的区别:Synchronized是⼀个同步锁。
当⼀个线程A访问synchronized修饰的代码块时,线程A就会获取该代码块的锁,如果这时存在其他线程范围该代码块时,将会阻塞,但是不影响这些线程访问其他⾮同步代码块。
ReentranLock是可重⼊锁。
由构造⽅法可知,该锁⽀持两种锁模式,公平锁和⾮公平锁。
默认是⾮公平的。
公平锁:当线程A获取访问该对象,获取到锁后,此时内部存在⼀个计数器num+1,其他线程想访问该对象,就会进⾏排队等待(等待队列最前⼀个线程处于待唤醒状态),直到线程A释放锁(num = 0),此时会唤醒处于待唤醒状态的线程进⾏获取锁的操作,⼀直循环。
java锁的实现原理
java锁的实现原理Java中的锁是一种并发控制机制,用于保护多个线程对共享资源的访问。
它的实现原理涉及到多线程同步和互斥的机制。
Java中的锁主要有两种实现方式:内置锁(synchronized关键字)和显式锁(Lock接口的实现类)。
1. 内置锁(synchronized关键字)的实现原理:- 内置锁是每个Java对象都具有的,它是通过监视器锁(monitor)来实现的。
- 当线程执行到synchronized块时,它会首先尝试获取对象的监视器锁,如果该锁没有被其他线程占用,那么线程就会获得锁并继续执行。
如果锁已经被其他线程占用,那么该线程就会进入阻塞状态,直到获取到锁为止。
- 当线程执行完synchronized块后,会释放对象的监视器锁,其他等待获取锁的线程就有机会获取到锁并执行。
2. 显式锁(Lock接口的实现类)的实现原理:- 显式锁是通过Lock接口及其实现类(如ReentrantLock)来实现的。
- 显式锁需要手动地获取和释放锁,相比于内置锁,它提供了更细粒度的控制。
- 显式锁通过一个底层的同步器来实现,最常用的同步器是AbstractQueuedSynchronizer(AQS)。
- AQS使用一个双向链表来保存等待获取锁的线程,并通过CAS(Compare and Swap)操作来更新链表的状态。
- 当线程调用Lock接口的lock()方法时,它会尝试获取锁,如果成功获取到锁,则继续执行;如果未能获取到锁,则线程会进入阻塞状态。
- 当线程调用Lock接口的unlock()方法时,它会释放锁,并通知等待队列中的某个线程继续尝试获取锁。
总的来说,Java锁的实现原理是通过底层的同步机制和线程状态来实现线程的同步和互斥,以保证对共享资源的正确访问。
内置锁使用对象的监视器锁来实现,而显式锁通过Lock接口及其实现类来实现。
synchronized和LOCK的实现---Java之锁研究
Lock和synchronizedJDK1.5以后,在锁机制方面引入了新的锁-Lock,在网上的说法都比较笼统,结合网上的信息和我的理解这里做个总结。
java现有的锁机制有两种实现方式,J.DK1.4前是通过synchronized实现,JDK1.5后加入java.util.concurrent.locks包下的各种lock(以下简称Lock)先说说代码层的区别。
synchronized:在代码里,synchronized类似“面向对象”,修饰类、方法、对象。
Lock:不作为修饰,类似“面向过程”,在方法中需要锁的时候lock,在结束的时候unlock (一般都在finally块里)。
例如代码:Java代码1public void method1() {2synchronized(this){//旧锁,无需人工释放3System.out.println(1);4}5}67public void method2() {8Lock lock = new ReentrantLock();9lock.lock();//上锁10try{11System.out.println(2);12}finally{13lock.unlock();//解锁14}15}其次说说性能。
相关的性能测试网上已经有很多,这里也直接拿来主义,给出结论:在并发高是,luck性能优势很明显,在低并发时,synchronized也能取得优势。
具体的临界范围比较难定论,下面会讨论。
现在来分析它们具体的区别。
锁都是原子性的,也可以理解为锁是否在使用的标记,并且比较和设置这个标记的操作是原子性的,不同硬件平台上的jdk实现锁的相关方法都是native的(比如park/unpark),所以不同平台上锁的精确度的等级由这些native的方法决定。
所以网上经常可以看见的结论是“Lock比synchronized有更精确的原子操作”说的也是native方法(不得不感慨C才是硬件王道)。
Java线程同步的解决方案——synchronized与Lock
Java线程同步的解决方案——synchronized与Lock要说明线程同步问题首先要说明Java线程的两个特性,可见性和有序性。
多个线程之间是不能直接传递数据交互的,它们之间的交互只能通过共享变量来实现。
例如,假设在多个线程之间共享了Count类的一个对象,Count类有一个私有域num和一个公有方法count(),count()方法对num进行加1操作。
这个对象是被创建在主内存(堆内存)中,每个线程都有自己的工作内存(线程栈),工作内存存储了主内存Count对象的一个副本,当线程操作Count对象时,首先从主内存复制Count对象到工作内存中,然后执行代码count.count(),改变了num值,最后用工作内存Count刷新主内存Count。
当一个对象在多个内存中都存在副本时,如果一个内存修改了共享变量,其它线程也应该能够看到被修改后的值,此为可见性。
多个线程执行时,CPU对线程的调度是随机的,我们不知道当前程序被执行到哪步就切换到了下一个线程,一个最经典的例子就是银行汇款问题,一个银行账户存款100,这时一个人从该账户取10元,同时另一个人向该账户汇10元,那么余额应该还是100。
那么此时可能发生这种情况,A线程负责取款,B线程负责汇款,A从主内存读到100,B从主内存读到100,A执行减10操作,并将数据刷新到主内存,这时主内存数据100-10=90,而B 内存执行加10操作,并将数据刷新到主内存,最后主内存数据100+10=110,显然这是一个严重的问题,我们要保证A线程和B线程有序执行,先取款后汇款或者先汇款后取款,此为有序性。
先用代码来展示一下线程同步问题:[java] view plain copy 在CODE上查看代码片派生到我的代码片public class TraditionalThreadSynchronized {public static void main(String[] args) {final Outputter output = new Outputter();new Thread() {public void run() {output.output("zhangsan");}}.start();new Thread() {public void run() {output.output("lisi");}}.start();}}class Outputter {public void output(String name) {// TODO 为了保证对name的输出不是一个原子操作,这里逐个输出name的每个字符for(int i = 0; i < name.length(); i++) {System.out.print(name.charAt(i));Thread.sleep(10);}}}运行结果:[java] view plain copy 在CODE上查看代码片派生到我的代码片zhliansigsan输出的字符串被打乱了,我们期望的输出结果是zhangsanlisi。
java lock底层实现原理
java lock底层实现原理Java中的锁机制是多线程编程中非常重要的一部分,它能够确保线程安全,避免多个线程同时访问共享资源造成数据不一致的情况。
在Java中,锁的底层实现原理涉及到 synchronized 关键字、Lock 接口以及 volatile 关键字等。
本文将深入探讨Java中锁的底层实现原理。
在Java中,最常用的锁机制是 synchronized 关键字。
synchronized 关键字是Java提供的一种内置锁,它可以用来对代码块或方法进行同步。
当一个线程进入synchronized代码块或方法时,会尝试获取对象的锁,如果锁已经被其他线程获取,则当前线程会被阻塞,直到锁被释放。
synchronized 关键字的底层实现是通过对象头中的 Mark Word 和 Monitor 对象来实现的。
Mark Word 是对象头中的一部分,用来存储对象的一些元数据信息,比如对象的哈希码、锁状态等。
在synchronized 关键字中,Mark Word 用来存储锁的信息,比如锁的拥有者、锁的状态等。
当一个线程获取到锁时,会将锁的拥有者设置为当前线程,当锁被释放时,会将锁的拥有者置为null。
通过对Mark Word的读写操作,可以实现锁的获取和释放。
Monitor 对象是一个与每个对象关联的锁对象,用来实现线程的同步和互斥。
在synchronized 关键字中,每个对象都会有一个关联的Monitor对象,用来实现锁的获取和释放。
Monitor对象中包含了线程的等待队列、锁的拥有者等信息,通过对Monitor对象的操作,可以实现锁的竞争和等待。
除了synchronized 关键字,Java中还提供了Lock接口,用来实现更加灵活的锁机制。
Lock接口提供了锁的获取、释放、等待等操作,相比synchronized关键字,Lock接口提供了更多的功能和灵活性。
Lock接口的底层实现原理一般是通过CAS(Compare And Swap)操作来实现的,CAS操作是一种乐观锁的实现方式,通过原子操作来实现锁的获取和释放。
Lock锁机制详解Lock与的Synchronized区别
Lock锁机制详解Lock与的Synchronized区别本章内容涵盖Lock的使⽤讲解,可重⼊锁、读写锁。
Lock和Synchronized的对⽐等。
多线程⼀直Java开发中的难点,也是⾯试中的常客,趁着还有时间,打算巩固⼀下JUC⽅⾯知识,我想机会随处可见,但始终都是留给有准备的⼈的,希望我们都能加油沉下去,再浮上来,我想我们会变的不⼀样的。
⼀、什么是 LockLock 锁实现提供了⽐使⽤同步⽅法和语句可以获得的更⼴泛的锁操作。
⼆、锁类型可重⼊锁:在执⾏对象中所有同步⽅法不⽤再次获得锁可中断锁:在等待获取锁过程中可中断公平锁:按等待获取锁的线程的等待时间进⾏获取,等待时间长的具有优先获取锁权利读写锁:对资源读取和写⼊的时候拆分为2部分处理,读的时候可以多线程⼀起读,写的时候必须同步地写三、Lock接⼝public interface Lock {void lock(); //获得锁。
/**除⾮当前线程被中断,否则获取锁。
如果可⽤,则获取锁并⽴即返回。
如果锁不可⽤,则当前线程将出于线程调度⽬的⽽被禁⽤并处于休眠状态,直到发⽣以下两种情况之⼀:锁被当前线程获取;要么其他⼀些线程中断当前线程,⽀持中断获取锁。
如果当前线程:在进⼊此⽅法时设置其中断状态;要么获取锁时中断,⽀持中断获取锁,*/void lockInterruptibly() throws InterruptedException;/**仅在调⽤时空闲时才获取锁。
如果可⽤,则获取锁并⽴即返回值为true 。
如果锁不可⽤,则此⽅法将⽴即返回false值。
*/boolean tryLock();//⽐上⾯多⼀个等待时间boolean tryLock(long time, TimeUnit unit) throws InterruptedException;// 解锁void unlock();//返回绑定到此Lock实例的新Condition实例。
java面试题之synchronized和lock有什么区别
类别synchronizedlock 存在层次java 的关键字,在jvm 层⾯上是⼀个类锁的释放1、以获取锁的线程执⾏完同步代码,释放锁2、线程执⾏发⽣异常,jvm 会让线程释放锁在finally 中必须释放锁,不然容易造成线程死锁锁的获取假设A 线程获得锁,B 线程等待,如果A 线程阻塞,B 线程会⼀直等待分情况⽽定,lock 有多个锁获取的⽅法,可以尝试获得锁,线程可以不⽤功⼀直等待锁状态⽆法判断可以判断锁类型可以重⼊,不可以中断,⾮公平可重⼊ 可以判断 可公平性能少量同步⼤量同步锁状态 25bit 4bit1bit 2bit 23bit2bit是否是偏向锁锁标志位 ⽆锁状态对象hashCode 、对象分代年龄01轻量级锁 指向锁记录的指针00 重量级锁 指向重量级锁的指针10 GC 标记 空,不需要记录信息11偏向锁线程IDEpoch 对象分代年龄101java ⾯试题之synchronized 和lock 有什么区别synchronized 和lock 的区别:synchronized 使⽤⽅式及原理:作⽤在⽅法上:public synchronized void test(){}//作⽤在⽅法上JVM 采⽤ACC_SYNCHRONIZED 标记符来实现同步的;作⽤在代码块上:synchronized (SynchronizedTest.class ){}//作⽤在同步代码块上JVM 是采⽤monitorenter 和monitorexit 两个指令来实现同步的;java 对象头: synchronized ⽤的锁是存在java 对象头⾥的,java 对象头⼀般占有两个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit ),那么什么是java 对象头呢?HotSpot 虚拟机的对象头主要包括两部分数据:Mark Word (标记字段)、Klass Pointer (类型指针)。
synchrnoized和reentrantlock的底层实现及重入的底层原理
synchrnoized和reentrantlock的底层实现及重入的底层原理`synchronized` 和 `ReentrantLock` 是 Java 中的两种不同的线程同步机制。
synchronized1. synchronized 是一个内置的特性,通过关键字来实现同步,可以在方法上使用,也可以在代码块上使用。
2. 同一时间只能有一个线程能够获得 synchronized 修饰的方法或者代码块,如果有其他线程想要访问 synchronized 修饰的代码块或者方法,就必须等待当前线程释放锁。
3. synchronized 的锁是可重入的,也就是说一个线程可以多次获得同一个锁。
4. synchronized 的锁是隐式的,不需要程序员显式地获取和释放。
5. synchronized 的锁是公平的,也就是说,当多个线程尝试获取同一把锁时,线程的排列顺序是有序的。
6. synchronized 的锁的粒度较大,它可以对整个方法或者整个代码块进行加锁。
ReentrantLock1. ReentrantLock 是 Java 中的一个类,通过该类可以实现线程同步。
2. ReentrantLock 和 synchronized 的功能相似,但是使用方式更为灵活,可以通过 `lock()` 和 `unlock()` 方法显式地获取和释放锁。
3. ReentrantLock 的锁不是可重入的,也就是说一个线程不能多次获得同一个锁。
如果一个线程尝试多次获取同一把锁,那么该线程会被挂起,直到它释放锁。
4. ReentrantLock 的锁不是隐式的,需要程序员显式地获取和释放。
5. ReentrantLock 的锁是非公平的,也就是说,当多个线程尝试获取同一把锁时,线程的排列顺序是无序的。
6. ReentrantLock 的锁的粒度较小,它只对一个特定的对象进行加锁。
重入的底层原理重入是指一个线程可以多次获得同一个锁。
synchronized与Lock的区别与使用
synchronized与Lock的区别与使⽤版权声明:本⽂为博主原创⽂章,未经博主允许不得转载。
https:///u012403290/article/details/64910926引⾔:昨天在学习别⼈分享的⾯试经验时,看到Lock的使⽤。
想起⾃⼰在上次⾯试也遇到了synchronized与Lock的区别与使⽤。
于是,我整理了两者的区别和使⽤情况,同时,对synchronized的使⽤过程⼀些常见问题的总结,最后是参照源码和说明⽂档,对Lock的使⽤写了⼏个简单的Demo。
请⼤家批评指正。
技术点:1、线程与进程:在开始之前先把进程与线程进⾏区分⼀下,⼀个程序最少需要⼀个进程,⽽⼀个进程最少需要⼀个线程。
关系是线程–>进程–>程序的⼤致组成结构。
所以线程是程序执⾏流的最⼩单位,⽽进程是系统进⾏资源分配和调度的⼀个独⽴单位。
以下我们所有讨论的都是建⽴在线程基础之上。
2、Thread的⼏个重要⽅法:我们先了解⼀下Thread的⼏个重要⽅法。
a、start()⽅法,调⽤该⽅法开始执⾏该线程;b、stop()⽅法,调⽤该⽅法强制结束该线程执⾏;c、join⽅法,调⽤该⽅法等待该线程结束。
d、sleep()⽅法,调⽤该⽅法该线程进⼊等待。
e、run()⽅法,调⽤该⽅法直接执⾏线程的run()⽅法,但是线程调⽤start()⽅法时也会运⾏run()⽅法,区别就是⼀个是由线程调度运⾏run()⽅法,⼀个是直接调⽤了线程中的run()⽅法!!看到这⾥,可能有些⼈就会问啦,那wait()和notify()呢?要注意,其实wait()与notify()⽅法是Object的⽅法,不是Thread的⽅法!!同时,wait()与notify()会配合使⽤,分别表⽰线程挂起和线程恢复。
这⾥还有⼀个很常见的问题,顺带提⼀下:wait()与sleep()的区别,简单来说wait()会释放对象锁⽽sleep()不会释放对象锁。
(转)synchronized和lock的区别
(转)synchronized和lock的区别背景:最近在准备java基础知识,对于可重⼊锁⼀直没有个清晰的认识,有必要对这块知识进⾏总结。
1 . 什么是可重⼊锁锁的概念就不⽤多解释了,当某个线程A已经持有了⼀个锁,当线程B尝试进⼊被这个锁保护的代码段的时候.就会被阻塞.⽽锁的操作粒度是”线程”,⽽不是调⽤(⾄于为什么要这样,下⾯解释).同⼀个线程再次进⼊同步代码的时候.可以使⽤⾃⼰已经获取到的锁,这就是可重⼊锁java⾥⾯内置锁(synchronize)和Lock(ReentrantLock)都是可重⼊的2 . 为什么要可重⼊如果线程A继续再次获得这个锁呢?⽐如⼀个⽅法是synchronized,递归调⽤⾃⼰,那么第⼀次已经获得了锁,第⼆次调⽤的时候还能进⼊吗? 直观上当然需要能进⼊.这就要求必须是可重⼊的.可重⼊锁⼜叫做递归锁,再举个例⼦.public class Widget {public synchronized void doSomething() {...}}public class LoggingWidget extends Widget {public synchronized void doSomething() {System.out.println(toString() + ": calling doSomething");super.doSomething();//若内置锁是不可重⼊的,则发⽣死锁}}这个例⼦是java并发编程实战中的例⼦.synchronized 是⽗类Widget的内置锁,当执⾏⼦类的⽅法的时候,先获取了⼀次Widget的锁,然后在执⾏super的时候,就要获取⼀次,如果不可重⼊,那么就跪了.3 . 如何实现可重⼊锁为每个锁关联⼀个获取计数器和⼀个所有者线程,当计数值为0的时候,这个所就没有被任何线程持有.当线程请求⼀个未被持有的锁时,JVM将记下锁的持有者,并且将获取计数值置为1,如果同⼀个线程再次获取这个锁,计数值将递增,退出⼀次同步代码块,计算值递减,当计数值为0时,这个锁就被释放.ReentrantLock源码⾥⾯有实现ps:可重⼊是指对同⼀线程⽽⾔。
synchronized和Lock的区别
synchronized和Lock的区别并发编程中,锁是经常需要⽤到的,今天我们⼀起来看下Java中的锁机制:synchronized和lock。
synchronizedSynchronized 是Java 并发编程中很重要的关键字,另外⼀个很重要的是 volatile。
Syncronized 的⽬的是⼀次只允许⼀个线程进⼊由他修饰的代码段,从⽽允许他们进⾏⾃我保护。
Synchronized 很像⽣活中的锁例⼦,进⼊由Synchronized 保护的代码区⾸先需要获取 Synchronized 这把锁,其他线程想要执⾏必须进⾏等待。
Synchronized 锁住的代码区域执⾏完成后需要把锁归还,也就是释放锁,这样才能够让其他线程使⽤。
它提供了⼀种独占的加锁⽅式。
Synchronized的获取和释放锁由JVM实现,⽤户不需要显⽰的释放锁,⾮常⽅便。
然⽽synchronized也有⼀定的局限性:当线程尝试获取锁的时候,如果获取不到锁会⼀直阻塞。
属于是⼀种悲观锁。
如果获取锁的线程进⼊休眠或者阻塞,除⾮当前线程异常,否则其他线程尝试获取锁必须⼀直等待。
synchronized可以锁代码块、⽅法和对象。
⽅法声明时使⽤,放在范围操作符之后,返回类型声明之前。
即⼀次只能有⼀个线程进⼊该⽅法,其他线程要想在此时调⽤该⽅法,只能排队等候。
当作⽤于静态⽅法时,锁住的是Class实例,⼜因为Class的相关数据存储在永久带PermGen(jdk1.8 则是 metaspace),永久带是全局共享的,因此静态⽅法锁相当于类的⼀个全局锁,会锁所有调⽤该⽅法的线程;private int number;public synchronized void numIncrease(){number++;}你也可以在某个代码块上使⽤ Synchronized 关键字,表⽰只能有⼀个线程进⼊某个代码段。
public void numDecrease(Object num){synchronized (num){number++;}}synchronized后⾯括号⾥是⼀对象,锁住的是所有以该对象为锁的代码块,此时线程获得的是对象锁。
Synchronized和Lock的区别,以及使用Lock的好处
Synchronized和Lock的区别,以及使⽤Lock的好处Synchronized 和 Lock 在java并发编程中⼴泛使⽤,简单的来说下这两者的区别,记录下⼀,两者的构造 synchronized是关键字,它是属于JVM层⾯的 Lock是⼀个具体的类,它是属于API层⾯的锁 (java.util.concurrent.locks.Lock)synchronized底层是通过monitor对象来完成⼆,使⽤⽅法 synchronized 不需要⽤户⼿动去释放锁,当synchronized代码执⾏完后系统会⾃动让线程释放对锁的占⽤ ReentrantLock则需要⽤户去⼿动释放锁,若没有主动释放锁,可能会导致死锁现象。
需要lock()和unlock()⽅法配合try/finally语句块来完成三,等待是否可以中断 synchronized不可中断,除⾮抛出异常或者正常运⾏完成 ReentrantLock 可中断: 1),设置超时⽅法tryLock(long timeout,TimeUnit unit) 2),lockInterruptibly()放代码块中,调⽤interrupt()⽅法可中断四,加锁是否公平 synchronized ⾮公平锁 ReentrantLock两者都可以,默认⾮公平锁,构造⽅法可以传⼊boolean值,true为公平锁,false为⾮公平锁五,锁绑定多个条件Condition synchronized 没有 ReentrantLock⽤来实现分组唤醒需要唤醒的线程们,可以精确唤醒,⽽不是像synchronized要么随机唤醒⼀个要么唤醒全部线程。
Lock的好处,在第五点看来绑定多个Condition 可以精确唤醒,⽤⼀下列⼦来看看:/**** 编写⼀个线程,开启三个线程,这三个线程的ID分别为A,B,C,每个线程将⾃⼰的ID在屏幕上打印10遍,要求输出结果必须按顺序显⽰。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
JVM底层又是如何实现synchronized的?目前在Java中存在两种锁机制:synchronized和Lock,Lock接口及其实现类是JDK5增加的内容,其作者是大名鼎鼎的并发专家Doug Lea。
本文并不比较synchronized与Lock 孰优孰劣,只是介绍二者的实现原理。
数据同步需要依赖锁,那锁的同步又依赖谁?synchronized给出的答案是在软件层面依赖JVM,而Lock给出的方案是在硬件层面依赖特殊的CPU指令,大家可能会进一步追问:JVM底层又是如何实现synchronized的?本文所指说的JVM是指Hotspot的6u23版本,下面首先介绍synchronized的实现:synrhronized关键字简洁、清晰、语义明确,因此即使有了Lock接口,使用的还是非常广泛。
其应用层的语义是可以把任何一个非null对象作为"锁",当synchronized作用在方法上时,锁住的便是对象实例(this);当作用在静态方法时锁住的便是对象对应的Class实例,因为Class数据存在于永久带,因此静态方法锁相当于该类的一个全局锁;当synchronized作用于某一个对象实例时,锁住的便是对应的代码块。
在HotSpot JVM实现中,锁有个专门的名字:对象监视器。
1. 线程状态及状态转换当多个线程同时请求某个对象监视器时,对象监视器会设置几种状态用来区分请求的线程:Contention List:所有请求锁的线程将被首先放置到该竞争队列Entry List:Contention List中那些有资格成为候选人的线程被移到Entry ListWait Set:那些调用wait方法被阻塞的线程被放置到Wait SetOnDeck:任何时刻最多只能有一个线程正在竞争锁,该线程称为OnDeckOwner:获得锁的线程称为Owner!Owner:释放锁的线程下图反映了个状态转换关系:新请求锁的线程将首先被加入到ConetentionList中,当某个拥有锁的线程(Owner状态)调用unlock之后,如果发现EntryList为空则从ContentionList中移动线程到EntryList,下面说明下ContentionList和EntryList 的实现方式:1.1 ContentionList虚拟队列ContentionList并不是一个真正的Queue,而只是一个虚拟队列,原因在于ContentionList 是由Node及其next指针逻辑构成,并不存在一个Queue的数据结构。
ContentionList是一个后进先出(LIFO)的队列,每次新加入Node时都会在队头进行,通过CAS改变第一个节点的的指针为新增节点,同时设置新增节点的next指向后续节点,而取得操作则发生在队尾。
显然,该结构其实是个Lock- Free的队列。
因为只有Owner线程才能从队尾取元素,也即线程出列操作无争用,当然也就避免了CAS的ABA问题。
1.2 EntryListEntryList与ContentionList逻辑上同属等待队列,ContentionList会被线程并发访问,为了降低对ContentionList队尾的争用,而建立EntryList。
Owner线程在unlock时会从ContentionList中迁移线程到EntryList,并会指定EntryList中的某个线程(一般为Head)为Ready(OnDeck)线程。
Owner线程并不是把锁传递给OnDeck线程,只是把竞争锁的权利交给OnDeck,OnDeck线程需要重新竞争锁。
这样做虽然牺牲了一定的公平性,但极大的提高了整体吞吐量,在Hotspot中把OnDeck的选择行为称之为“竞争切换”。
OnDeck线程获得锁后即变为owner线程,无法获得锁则会依然留在EntryList中,考虑到公平性,在EntryList中的位置不发生变化(依然在队头)。
如果Owner线程被wait方法阻塞,则转移到WaitSet队列;如果在某个时刻被notify/notifyAll唤醒,则再次转移到EntryList。
2. 自旋锁那些处于ContetionList、EntryList、WaitSet中的线程均处于阻塞状态,阻塞操作由操作系统完成(在Linxu下通过pthread_mutex_lock函数)。
线程被阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能缓解上述问题的办法便是自旋,其原理是:当发生争用时,若Owner线程能在很短的时间内释放锁,则那些正在争用线程可以稍微等一等(自旋),在Owner线程释放锁后,争用线程可能会立即得到锁,从而避免了系统阻塞。
但Owner运行的时间可能会超出了临界值,争用线程自旋一段时间后还是无法获得锁,这时争用线程则会停止自旋进入阻塞状态(后退)。
基本思路就是自旋,不成功再阻塞,尽量降低阻塞的可能性,这对那些执行时间很短的代码块来说有非常重要的性能提高。
自旋锁有个更贴切的名字:自旋-指数后退锁,也即复合锁。
很显然,自旋在多处理器上才有意义。
还有个问题是,线程自旋时做些啥?其实啥都不做,可以执行几次for循环,可以执行几条空的汇编指令,目的是占着CPU不放,等待获取锁的机会。
所以说,自旋是把双刃剑,如果旋的时间过长会影响整体性能,时间过短又达不到延迟阻塞的目的。
显然,自旋的周期选择显得非常重要,但这与操作系统、硬件体系、系统的负载等诸多场景相关,很难选择,如果选择不当,不但性能得不到提高,可能还会下降,因此大家普遍认为自旋锁不具有扩展性。
自旋优化策略对自旋锁周期的选择上,HotSpot认为最佳时间应是一个线程上下文切换的时间,但目前并没有做到。
经过调查,目前只是通过汇编暂停了几个CPU周期,除了自旋周期选择,HotSpot还进行许多其他的自旋优化策略,具体如下:如果平均负载小于CPUs则一直自旋如果有超过(CPUs/2)个线程正在自旋,则后来线程直接阻塞如果正在自旋的线程发现Owner发生了变化则延迟自旋时间(自旋计数)或进入阻塞如果CPU处于节电模式则停止自旋自旋时间的最坏情况是CPU的存储延迟(CPU A存储了一个数据,到CPU B得知这个数据直接的时间差)自旋时会适当放弃线程优先级之间的差异那synchronized实现何时使用了自旋锁?答案是在线程进入ContentionList时,也即第一步操作前。
线程在进入等待队列时首先进行自旋尝试获得锁,如果不成功再进入等待队列。
这对那些已经在等待队列中的线程来说,稍微显得不公平。
还有一个不公平的地方是自旋线程可能会抢占了Ready线程的锁。
自旋锁由每个监视对象维护,每个监视对象一个。
3. JVM1.6偏向锁在JVM1.6中引入了偏向锁,偏向锁主要解决无竞争下的锁性能问题,首先我们看下无竞争下锁存在什么问题:现在几乎所有的锁都是可重入的,也即已经获得锁的线程可以多次锁住/解锁监视对象,按照之前的HotSpot设计,每次加锁/解锁都会涉及到一些CAS操作(比如对等待队列的CAS 操作),CAS操作会延迟本地调用,因此偏向锁的想法是一旦线程第一次获得了监视对象,之后让监视对象“偏向”这个线程,之后的多次调用则可以避免CAS操作,说白了就是置个变量,如果发现为true则无需再走各种加锁/解锁流程。
但还有很多概念需要解释、很多引入的问题需要解决:3.1 CAS及SMP架构CAS为什么会引入本地延迟?这要从SMP(对称多处理器)架构说起,下图大概表明了SMP的结构:其意思是所有的CPU会共享一条系统总线(BUS),靠此总线连接主存。
每个核都有自己的一级缓存,各核相对于BUS对称分布,因此这种结构称为“对称多处理器”。
而CAS的全称为Compare-And-Swap,是一条CPU的原子指令,其作用是让CPU比较后原子地更新某个位置的值,经过调查发现,其实现方式是基于硬件平台的汇编指令,就是说CAS是靠硬件实现的,JVM只是封装了汇编调用,那些AtomicInteger类便是使用了这些封装后的接口。
Core1和Core2可能会同时把主存中某个位置的值Load到自己的L1 Cache中,当Core1在自己的L1 Cache中修改这个位置的值时,会通过总线,使Core2中L1 Cache对应的值“失效”,而Core2一旦发现自己L1 Cache中的值失效(称为Cache命中缺失)则会通过总线从内存中加载该地址最新的值,大家通过总线的来回通信称为“Cache一致性流量”,因为总线被设计为固定的“通信能力”,如果Cache一致性流量过大,总线将成为瓶颈。
而当Core1和Core2中的值再次一致时,称为“Cache一致性”,从这个层面来说,锁设计的终极目标便是减少Cache一致性流量。
而CAS恰好会导致Cache一致性流量,如果有很多线程都共享同一个对象,当某个Core CAS成功时必然会引起总线风暴,这就是所谓的本地延迟,本质上偏向锁就是为了消除CAS,降低Cache一致性流量。
Cache一致性:上面提到Cache一致性,其实是有协议支持的,现在通用的协议是MESI(最早由Intel 开始支持),具体参考:/wiki/MESI_protocol,以后会仔细讲解这部分。
Cache一致性流量的例外情况:其实也不是所有的CAS都会导致总线风暴,这跟Cache一致性协议有关,具体参考:/dave/entry/biased_locking_in_hotspotNUMA(Non Uniform Memory Access Achitecture)架构:与SMP对应还有非对称多处理器架构,现在主要应用在一些高端处理器上,主要特点是没有总线,没有公用主存,每个Core有自己的内存,针对这种结构此处不做讨论。
3.2 偏向解除偏向锁引入的一个重要问题是,在多争用的场景下,如果另外一个线程争用偏向对象,拥有者需要释放偏向锁,而释放的过程会带来一些性能开销,但总体说来偏向锁带来的好处还是大于CAS代价的。
4. 总结关于锁,JVM中还引入了一些其他技术比如锁膨胀等,这些与自旋锁、偏向锁相比影响不是很大,这里就不做介绍。
通过上面的介绍可以看出,synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。
JVM中的另一种锁Lock的实现前文(深入JVM锁机制-synchronized)分析了JVM中的synchronized实现,本文继续分析JVM中的另一种锁Lock的实现。
与synchronized不同的是,Lock完全用Java写成,在java这个层面是无关JVM实现的。