精华内容
下载资源
问答
  • java中的锁

    千次阅读 2020-07-20 18:07:41
    java中的锁 1.乐观VS悲观 乐观锁适合读操作多的场景,悲观锁适合写操作多的场景 1.悲观锁 数据库 select column from table where column='xx' for update; synchronized Lock 2.乐观锁 1.版本号 ...

    java中的锁

    1.乐观VS悲观
    	乐观锁适合读操作多的场景,悲观锁适合写操作多的场景
    	1.悲观锁
    		数据库 select column from table where column='xx' for update;
    		synchronized
    		Lock
    	2.乐观锁
    		1.版本号
    			update 表 set 金额 = 120,version = version + 1 where 金额 = 100 and version = 0;
    		2.CAS(Compare-and-Swap比较并替换)
    			1.需要读写的内存值 V  进行比较的值 A  要写入的新值 B
    				当且仅当 V 的值等于 A 时,CAS通过原子方式用新值B来更新V的值(“比较+更新”整体是一个原子操作),否则不会执行任何操作。
    				一般情况下,“更新”是一个不断重试的操作
    			2.实现 AtomicReference AtomicStampedReference
    2.自旋锁 VS 适应性自旋锁
    	自旋锁
    		阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间并且开销很大。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长。
    		自旋锁本身是有缺点的,它不能代替阻塞。自旋等待虽然避免了线程切换的开销,但它要占用处理器时间。如果锁被占用的时间很短,自旋等待的效果就会非常好。反之,如果锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源。所以,自旋等待的时间必须要有一定的限度,如果自旋超过了限定次数(默认是10次,可以使用-XX:PreBlockSpin来更改)没有成功获得锁,就应当挂起线程。
    		实现 AtomicInteger中调用unsafe进行自增操作的源码中的do-while循环就是一个自旋操作
    	适应性自旋锁
    		自适应意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。
    		自旋锁在JDK1.4.2中引入,使用-XX:+UseSpinning来开启。JDK 6中变为默认开启,并且引入了自适应的自旋锁(适应性自旋锁)。
    3.无锁 VS 偏向锁 VS 轻量级锁 VS 重量级锁
    	目前锁一共有4种状态,级别从低到高依次是:无锁、偏向锁、轻量级锁和重量级锁。锁状态只能升级不能降级
    	无锁
    		没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功 CAS是无锁
    	偏向锁
    		是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价
    		偏向锁在JDK 6及以后的JVM里是默认启用的。可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,关闭之后程序默认会进入轻量级锁状态。
    	轻量级锁
    		是指当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。
    		若当前只有一个等待线程,则该线程通过自旋进行等待。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁。
    	重量级锁
    		等待锁的线程都会进入阻塞状态
    		synchronized(原理 锁存在Java对象头里 每一个Java对象就有一把看不见的锁,称为内部锁或者Monitor锁) 这种依赖于操作系统Mutex Lock所实现的锁我们称之为重量级锁
    4.公平锁 VS 非公平锁
    	公平锁
    		是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。
    	非公平锁
    		是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。
    5.可重入锁 VS 非可重入锁
    	可重入锁
    		可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
    	非可重入锁
    		相反
    	比较
    		当线程尝试获取锁时,可重入锁先尝试获取并更新status值,如果status == 0表示没有其他线程在执行同步代码,则把status置为1,当前线程开始执行。如果status != 0,则判断当前线程是否是获取到这个锁的线程,如果是的话执行status+1,且当前线程可以再次获取锁。而非可重入锁是直接去获取并尝试更新当前status的值,如果status != 0的话会导致其获取锁失败,当前线程阻塞。
    		释放锁时,可重入锁同样先获取当前status的值,在当前线程是持有锁的线程的前提下。如果status-1 == 0,则表示当前线程所有重复获取锁的操作都已经执行完毕,然后该线程才会真正释放锁。而非可重入锁则是在确定当前线程是持有锁的线程之后,直接将status置为0,将锁释放。
    6.独享锁(EXCLUSIVE排他锁) VS 共享锁(SHARED)
    	独享锁
    		是指该锁一次只能被一个线程所持有。如果线程T对数据A加上排它锁后,则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据
    	共享锁
    		是指该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得共享锁的线程只能读数据,不能修改数据。
    	实现
    		ReadLock和WriteLock
    

    分类
    在这里插入图片描述乐观 悲观
    在这里插入图片描述自旋锁
    在这里插入图片描述
    可重入锁
    在这里插入图片描述

    展开全文
  • Java中的锁

    万次阅读 多人点赞 2016-04-20 21:38:47
    这里整理了Java中的各种,若有不足之处希望大家在下方留言探讨。 WARNING:本文适合有一定JAVA基础的同学阅读。 公平和非公平 公平是指多个线程在等待同一个时,必须按照申请的先后顺序来一次获得...

    欢迎支持笔者新作:《深入理解Kafka:核心设计与实践原理》和《RabbitMQ实战指南》,同时欢迎关注笔者的微信公众号:朱小厮的博客。


    欢迎跳转到本文的原文链接:https://honeypps.com/java/locks-in-java/

    在学习或者使用Java的过程中进程会遇到各种各样的锁的概念:公平锁、非公平锁、自旋锁、可重入锁、偏向锁、轻量级锁、重量级锁、读写锁、互斥锁等待。这里整理了Java中的各种锁,若有不足之处希望大家在下方留言探讨。

    WARNING:本文适合有一定JAVA基础的同学阅读。

    ##公平锁和非公平锁
    公平锁是指多个线程在等待同一个锁时,必须按照申请锁的先后顺序来一次获得锁。

    公平锁的好处是等待锁的线程不会饿死,但是整体效率相对低一些;非公平锁的好处是整体效率相对高一些,但是有些线程可能会饿死或者说很早就在等待锁,但要等很久才会获得锁。其中的原因是公平锁是严格按照请求所的顺序来排队获得锁的,而非公平锁时可以抢占的,即如果在某个时刻有线程需要获取锁,而这个时候刚好锁可用,那么这个线程会直接抢占,而这时阻塞在等待队列的线程则不会被唤醒。

    公平锁可以使用new ReentrantLock(true)实现。

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

    虚拟机的开发团队注意到在许多应用上,共享数据的锁定状态只会持续很短的一段时间,为了这段时间取挂起和恢复现场并不值得。如果物理机器有一个以上的处理器,能让两个或以上的线程同时并行执行,我们就可以让后面请求锁的那个线程“稍等一下“,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只需让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。

    自旋等待不能代替阻塞。自旋等待本身虽然避免了线程切换的开销,但它是要占用处理器时间的,因此,如果锁被占用的时间很短,自旋等待的效果就会非常好,反之,如果锁被占用的时间很长,那么自旋的线程只会拜拜浪费处理器资源。因此,自旋等待的时间必须要有一定的限度,如果自旋超过了限定次数(默认是10次,可以使用-XX:PreBlockSpin来更改)没有成功获得锁,就应当使用传统的方式去挂起线程了。

    自旋锁在JDK1.4.2中引入,使用-XX:+UseSpinning来开启。JDK6中已经变为默认开启,并且引入了自适应的自旋锁。自适应意味着自旋的时间不在固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。

    自旋是在轻量级锁中使用的,在重量级锁中,线程不使用自旋。

    如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间,比如100次循环。另外,如果对于某个锁,自旋很少成功获得过,那在以后要获取这个锁时将可能省略掉自旋过程,以避免浪费处理器资源。

    ##锁消除
    锁消除是虚拟机JIT在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。锁消除的主要判断依据是来源于逃逸分析的数据支持,如果判断在一段代码中,堆上的所有数据都不会逃逸出去从而能被其他线程访问到,那就可以把他们当做栈上数据对待,认为他们是线程私有的,同步加锁自然就无需进行。

    来看这样一个方法:

        public String concatString(String s1, String s2, String s3)
        {
            StringBuffer sb = new StringBuffer();
            sb.append(s1);
            sb.append(s2);
            sb.append(s3);
            return sb.toString();
        }
    

    可以知道StringBuffer 的append方法定义如下:

        public synchronized StringBuffer append(StringBuffer sb) {
            super.append(sb);
            return this;
        }
    

    也就是说在concatString()方法中涉及了同步操作。但是可以观察到sb对象它的作用域被限制在方法的内部,也就是sb对象不会“逃逸”出去,其他线程无法访问。因此,虽然这里有锁,但是可以被安全的消除,在即时编译之后,这段代码就会忽略掉所有的同步而直接执行了。

    ##锁粗化
    原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制的尽量小——只在共享数据的实际作用域中才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁禁止,那等待的线程也能尽快拿到锁。大部分情况下,这些都是正确的。但是,如果一些列的联系操作都是同一个对象反复加上和解锁,甚至加锁操作是出现在循环体中的,那么即使没有线程竞争,频繁地进行互斥同步操作也导致不必要的性能损耗。

    举个案例,类似锁消除的concatString()方法。如果StringBuffer sb = new StringBuffer();定义在方法体之外,那么就会有线程竞争,但是每个append()操作都对同一个对象反复加锁解锁,那么虚拟机探测到有这样的情况的话,会把加锁同步的范围扩展到整个操作序列的外部,即扩展到第一个append()操作之前和最后一个append()操作之后,这样的一个锁范围扩展的操作就称之为锁粗化。

    ##可重入锁
    可重入锁,也叫做递归锁,指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。

    在JAVA环境下 ReentrantLock 和synchronized 都是可重入锁。可重入锁最大的作用是避免死锁。

    ##类锁和对象锁
    类锁:在方法上加上static synchronized的锁,或者synchronized(xxx.class)的锁。如下代码中的method1和method2:

    对象锁:参考method4, method5,method6.

    public class LockStrategy
    {
        public Object object1 = new Object();
    
        public static synchronized void method1(){}
        public void method2(){
            synchronized(LockStrategy.class){}
        }
    
        public synchronized void method4(){}
        public void method5()
        {
            synchronized(this){}
        }
        public void method6()
        {
            synchronized(object1){}
        }
    }
    

    下面做一道习题来加深一下对对象锁和类锁的理解.
    有一个类这样定义:

    public class SynchronizedTest
    {
        public synchronized void method1(){}
        public synchronized void method2(){}
        public static synchronized void method3(){}
        public static synchronized void method4(){}
    }
    

    那么,有SynchronizedTest的两个实例a和b,对于一下的几个选项有哪些能被一个以上的线程同时访问呢?
    A. a.method1() vs. a.method2()
    B. a.method1() vs. b.method1()
    C. a.method3() vs. b.method4()
    D. a.method3() vs. b.method3()
    E. a.method1() vs. a.method3()
    答案是什么呢?BE

    ##偏向锁、轻量级锁和重量级锁
    synchronized的偏向锁、轻量级锁以及重量级锁是通过Java对象头实现的。博主在Java对象大小内幕浅析中提到了Java对象的内存布局分为:对象头、实例数据和对齐填充,而对象头又可以分为"Mark Word"和类型指针klass。"Mark Word"是关键,默认情况下,其存储对象的HashCode、分代年龄和锁标记位。

    这里说的都是以HotSpot虚拟机为基准的。首先来看一下"Mark Word"的内容:

    锁状态 存储内容 标志位
    无锁 对象的hashCode、对象分代年龄、是否是偏向锁(0) 01
    轻量级 指向栈中锁记录的指针 00
    重量级 指向互斥量(重量级锁)的指针 10
    GC标记 (空) 11
    偏向锁 偏向线程ID、偏向时间戳、对象分代年龄、是否是偏向锁(1) 01

    注意到这里的无锁和偏向锁在"Mark Word"的倒数第三bit中分别采用0和1标记。

    偏向锁是JDK6中引入的一项锁优化,它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。

    偏向锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要同步。大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。

    当锁对象第一次被线程获取的时候,线程使用CAS操作把这个锁的线程ID记录再对象Mark Word之中,同时置偏向标志位1。以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需要简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。

    如果线程使用CAS操作时失败则表示该锁对象上存在竞争并且这个时候另外一个线程获得偏向锁的所有权。当到达全局安全点(safepoint,这个时间点上没有正在执行的字节码)时获得偏向锁的线程被挂起,膨胀为轻量级锁(涉及Monitor Record,Lock Record相关操作,这里不展开),同时被撤销偏向锁的线程继续往下执行同步代码。

    当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束。

    线程在执行同步块之前,JVM会先在当前线程的栈帧中创建用于存储锁记录(Lock Record)的空间,并将对象头中的Mard Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。如果自旋失败则锁会膨胀成重量级锁。如果自旋成功则依然处于轻量级锁的状态。

    轻量级锁的解锁过程也是通过CAS操作来进行的,如果对象的Mark Word仍然指向线程的锁记录,那就用CAS操作把对象当前的Mark Word和线程中赋值的Displaced Mark Word替换回来,如果替换成功,整个同步过程就完成了,如果替换失败,就说明有其他线程尝试过获取该锁,那就要在释放锁的同时,唤醒被挂起的线程。

    轻量级锁提升程序同步性能的依据是:对于绝大部分的锁,在整个同步周期内都是不存在竞争的(区别于偏向锁)。这是一个经验数据。如果没有竞争,轻量级锁使用CAS操作避免了使用互斥量的开销,但如果存在锁竞争,除了互斥量的开销外,还额外发生了CAS操作,因此在有竞争的情况下,轻量级锁比传统的重量级锁更慢。

    整个synchronized锁流程如下:

    1. 检测Mark Word里面是不是当前线程的ID,如果是,表示当前线程处于偏向锁
    2. 如果不是,则使用CAS将当前线程的ID替换Mard Word,如果成功则表示当前线程获得偏向锁,置偏向标志位1
    3. 如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁。
    4. 当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,当前线程获得锁
    5. 如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
    6. 如果自旋成功则依然处于轻量级状态。
    7. 如果自旋失败,则升级为重量级锁。

    ##悲观锁和乐观锁
    悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。
    乐观锁:假定不会发生并发冲突,只在提交操作时检测是否违反数据完整性。(使用版本号或者时间戳来配合实现)

    ##共享锁和排它锁
    共享锁:如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排它锁。获准共享锁的事务只能读数据,不能修改数据。
    排它锁:如果事务T对数据A加上排它锁后,则其他事务不能再对A加任何类型的锁。获得排它锁的事务即能读数据又能修改数据。

    ##读写锁
    读写锁是一个资源能够被多个读线程访问,或者被一个写线程访问但不能同时存在读线程。Java当中的读写锁通过ReentrantReadWriteLock实现。具体使用方法这里不展开。

    ##互斥锁
    所谓互斥锁就是指一次最多只能有一个线程持有的锁。在JDK中synchronized和JUC的Lock就是互斥锁。

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

    1. 无状态编程。无状态代码有一些共同的特征:不依赖于存储在对上的数据和公用的系统资源、用到的状态量都由参数中传入、不调用非无状态的方法等。可以参考Servlet。
    2. 线程本地存储。可以参考ThreadLocal
    3. volatile
    4. CAS
    5. 协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。

    ##分段锁
    ConcurrentHashMap中采用了分段锁

    ##闭锁
    闭锁是一种同步工具类,可以延迟线程的进度直到其到达终止状态。闭锁的作用相当于一扇门:在闭锁到达结束状态之前,这扇门一直是关闭的,并且没有任何线程能通过,当到达结束状态时,这扇门会打开允许所有的线程通过。当闭锁到达结束状态后,将不会再改变状态,因此这扇门将永远保持打开状态。闭锁可以用来确保某些活动指导其他活动都完成后才继续执行。CountDownLatch就是一种灵活的闭锁实现。

    ##死锁
    死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,他们都将无法推进下去。这是一个严重的问题,因为死锁会让你的程序挂起无法完成任务,死锁的发生必须满足一下4个条件:

    • 互斥条件:一个资源每次只能被一个进程使用。
    • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
    • 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
    • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

    避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序做操作来避免死锁。

    ##活锁
    LiveLock是一种形式活跃性问题,该问题尽管不会阻塞线程,但也不能继续执行,因为线程将不断重复执行相同的操作,而且总会失败。活锁通常发送在处理事务消息的应用程序中:如果不能成功地处理某个消息,那么消息处理机制将回滚整个事务,并将它重新放到队列的开头:如果不能成功地处理某个消息,那么消息处理机制将回滚整个事务,并将它重新放到队列的开头。如果消息处理器在处理某种特定类型的消息时存在错误并导致它失败,那么每当这个消息从队列中取出并传递到存在错误的处理器时,都会发生事务回滚。由于这条消息又被放回到队列开头,因此处理器将被反复调用,并返回相同的结果。


    参考资料

    1. 《深入理解Java虚拟机》周志明著
    2. 《Java并发编程的艺术》方腾飞等著
    3. Java对象大小内幕浅析
    4. JVM内部细节之一:synchronized关键字及实现细节(轻量级锁Lightweight Locking)
    5. JVM内部细节之二:偏向锁(Biased Locking)

    欢迎跳转到本文的原文链接:https://honeypps.com/java/locks-in-java/

    欢迎支持笔者新作:《深入理解Kafka:核心设计与实践原理》和《RabbitMQ实战指南》,同时欢迎关注笔者的微信公众号:朱小厮的博客。


    展开全文
  • Java 中的锁

    2019-09-18 22:23:28
    今天是九一八事变爆发 88 周年,国耻日。 每当我看到祖国航空航天、互联网、5G 这些科技...在并发编程,经常会遇到多个线程访问同一个共享变量,当同时对共享变量进行读写操作时,就会产生数据不一致情况。...

    今天是九一八事变爆发 88 周年,国耻日。

    每当我看到祖国的航空航天、互联网、5G 这些科技方面赶超美国的新闻时,我内心都异常激动,从 "东亚病夫" 到吾辈自强,靠得是无数中华儿女的实干。

    铭记历史...


     

    工作再忙,还是希望坚持把编程知识分享下去,继续 Java 并发编程。

    在并发编程中,经常会遇到多个线程访问同一个共享变量,当同时对共享变量进行读写操作时,就会产生数据不一致的情况。


    为了解决这个问题

    • JDK 1.5 之前,使用 synchronized 关键字,拿到 Java 对象的锁,保护锁定的代码块。JVM 保证同一时刻只有一个线程可以拿到这个 Java 对象的锁,执行对应的代码块。

    • JDK 1.5 开始,引入了并发工具包 java.util.concurrent.locks.Lock,让锁的功能更加丰富。

     

    常见的锁

    • synchronized 关键字锁定代码库

    • 可重入锁 java.util.concurrent.lock.ReentrantLock

    • 可重复读写锁 java.util.concurrent.lock.ReentrantReadWriteLock

     

    Java 中不同维度的锁分类

    可重入锁

    • 指在同一个线程在外层方法获取锁的时候,进入内层方法会自动获取锁。JDK 中基本都是可重入锁,避免死锁的发生。上面提到的常见的锁都是可重入锁。

     

    公平锁 / 非公平锁

    • 公平锁,指多个线程按照申请锁的顺序来获取锁。如 java.util.concurrent.lock.ReentrantLock.FairSync

    • 非公平锁,指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程先获得锁。如 synchronized、java.util.concurrent.lock.ReentrantLock.NonfairSync

            
    独享锁 / 共享锁

    • 独享锁,指锁一次只能被一个线程所持有。synchronized、java.util.concurrent.locks.ReentrantLock 都是独享锁

    • 共享锁,指锁可被多个线程所持有。ReadWriteLock 返回的 ReadLock 就是共享锁

              
    悲观锁 / 乐观锁

    • 悲观锁,一律会对代码块进行加锁,如 synchronized、java.util.concurrent.locks.ReentrantLock

    • 乐观锁,默认不会进行并发修改,通常采用 CAS 算法不断尝试更新

    • 悲观锁适合写操作较多的场景,乐观锁适合读操作较多的场景

            
    粗粒度锁 / 细粒度锁

    • 粗粒度锁,就是把执行的代码块都锁定

    • 细粒度锁,就是锁住尽可能小的代码块,java.util.concurrent.ConcurrentHashMap 中的分段锁就是一种细粒度锁

    • 粗粒度锁和细粒度锁是相对的,没有什么标准

            
    偏向锁 / 轻量级锁 / 重量级锁

    • JDK 1.5 之后新增锁的升级机制,提升性能。

    • 通过 synchronized 加锁后,一段同步代码一直被同一个线程所访问,那么该线程获取的就是偏向锁

    • 偏向锁被一个其他线程访问时,Java 对象的偏向锁就会升级为轻量级锁

    • 再有其他线程会以自旋的形式尝试获取锁,不会阻塞,自旋一定次数仍然未获取到锁,就会膨胀为重量级锁        

            
    自旋锁

    • 自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环占有、浪费 CPU 资源

     

    下一篇,探究常用的锁的具体使用。

     


    【Java面试题与答案】整理推荐

     

    展开全文
  • Java中的闭锁

    千次阅读 2018-05-21 14:32:12
    闭锁 1.定义: ...其实也有些类似于之前CUDA编程用到 __syncthreads()方法去同步同一个块内线程。 2.实现 CountDownLatch是一种灵活闭锁实现。一般会把StartGate设置1,EndGate...

    闭锁

    1.定义:

    闭锁是一种同步工具,可以延迟线程直到其达到其终止状态。
    例如:DOTA2中匹配等待点确定界面的设计,需要等待所有十个玩家都点就绪才能继续进行。其实也有些类似于之前CUDA编程中用到的 __syncthreads()方法去同步同一个块内的线程。

    2.实现

    CountDownLatch是一种灵活的闭锁实现。一般会把StartGate设置1,EndGate设置为nThreads。通过await()方法等待计数器减到0,countdown()方法执行计数器减法。

    3.代码

    书本TestHarness类
    import java.util.concurrent.CountDownLatch;
    
    public class TestHarness {
        public long timetasks(int nThreads, final Runnable task) throws InterruptedException {
            // init start from 1.
            final CountDownLatch startGate = new CountDownLatch(1);
            final CountDownLatch endGate = new CountDownLatch(nThreads);
            for (int i=0;i< nThreads;i++){
                Thread t = new Thread(){
                    public void run(){
                        try{
                            startGate.await();//wait the startgate count down to zero, then run.
                            try{
                                task.run();
                            } finally {
                                endGate.countDown();
                            }
                        } catch (InterruptedException ignored){ }
                    }
                };
                t.start();
            }
            long start = System.nanoTime(); //单位纳秒
            startGate.countDown();
            endGate.await();//wait the endGate count down to zero, then run.
            long end = System.nanoTime();
            return end - start;
        }
    }
    
    
    我写的测试类
    public class Solution {
        public static void main(String[] args) throws InterruptedException {
            final Thread tt = new Thread(){
                @Override
                public void run() {
                    System.out.println("Thread Id: " + this.getId());  //具体的方法,比如DOTA2中点击准备就绪的按键
                }
            };
            TestHarness bisuo = new TestHarness();
            long threadtime = bisuo.timetasks(100,tt);
            double ans = (double)threadtime / 1000000000;
            System.out.println("Thread time:" +ans + "s");
        }
    }
    
    
    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 31,031
精华内容 12,412
关键字:

java中的锁

java 订阅