精华内容
下载资源
问答
  • Java锁机制详解.pdf

    2017-11-02 17:05:48
    Java锁机制详解.pdf java线程 java多线程 Java锁机制详解.pdf java线程 java多线程
  • 主要介绍了Java锁机制Lock用法,结合具体实例形式分析了Java锁机制的相关上锁、释放锁、隐式锁、显式锁等概念与使用技巧,需要的朋友可以参考下
  • 由浅入深解析synchronized机制,各种的概念的介绍,膨胀过程,基于redis的分布式demo。
  • Java锁机制

    千次阅读 2018-05-16 00:03:00
    对于熟悉java多线程并发的人来说,java锁机制是不可逃避的话题。那么什么是java锁机制,以及什么时候使用java的锁呢?让我们看看以下几种场景吧!一、同步锁案例:假设现在我们现在有很多人去商店买衣服,因为我们每...

    对于熟悉java多线程并发的人来说,java锁机制是不可逃避的话题。那么什么是java锁机制,以及什么时候使用java的锁呢?

    让我们看看以下几种场景吧!

    一、同步锁

    案例:假设现在我们现在有很多人去商店买衣服,因为我们每个人买衣服是可能同时执行的,不可能商店要求同一时刻只能有一个顾客买衣服,因此我们需要为每一个顾客买衣服设置一个线程;假设买衣服的整个过程有“挑选”、“试衣服”、“结账”三个过程,那么初步代码实现是这样的:

    public class Shopping {
    	public static void main(String[] args) {
    		Task task = new Task();
    		new Thread(task).start();
    		new Thread(task).start();
    	}
    }
    class Task implements Runnable{
    	public void run(){
    		System.out.println("顾客正在挑选衣服...");
    		try {
    			Thread.sleep(2000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		System.out.println("顾客正在试衣服...");
    		try {
    			Thread.sleep(2000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		System.out.println("顾客正在结账...");
    	}
    }

    执行结果:

    顾客正在挑选衣服...
    顾客正在挑选衣服...
    顾客正在试衣服...
    顾客正在试衣服...
    顾客正在结账...

    顾客正在结账...

    这样的结果大家是不是觉得有点别扭呢?我们挑选衣服、结账还可以一起,那没问题,但是试衣服就有点尴尬了,万一大家性别不同呢?。。。所以这时候要是有一把锁在我们换衣服的时候能够把试衣间锁上,一个人试完了再到下一个那样就不会乱套了,那么怎么做呢?其实java提供了一个关键字synchronized来修饰我们多个线程需要同步执行的代码段或方法,所谓同步就是一个一个排队干的意思,具体语法如下:

    synchronized(监视器){

        需要执行的代码

    }

    //监视器:java里面的任意一个对象,但是要求该对象对于需要同步执行的线程来说是同一个对象即可。

    那么以上程序就可以这么干了:

    public class Shopping {
    	public static void main(String[] args) {
    		Task task = new Task();
    		new Thread(task).start();
    		new Thread(task).start();
    	}
    }
    class Task implements Runnable{
    	public void run(){
    		......................................
    		synchronized(this){
    			System.out.println("顾客正在试衣服...");
    			try {
    				Thread.sleep(2000);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    		............................
    	}
    }

    结果:

    顾客正在挑选衣服...
    顾客正在挑选衣服...
    顾客正在试衣服...
    顾客正在结账...
    顾客正在试衣服...

    顾客正在结账...

    是不是发现试衣服这件事错开了呢?那么这就是java里面的同步锁机制:当一个线程执行synchronized修饰的代码段时,会给监视器上一把锁,当另一个线程执行同样的被synchronized修饰的任务时,该线程就需要关注该监视器有没有上锁,没有才能执行,否则就阻塞。

    二、互斥锁

    案例:假设有这样一个场景,对于一家人来说每个人都可以同时吃饭同时看电视,但是有一个家规就是吃饭的时候谁也不准看电视,那么怎样实现代码模拟呢?看下面:

    public class Shopping {
    	public static void main(String[] args) {
    		Task task = new Task();
    		new Thread(task).start();
    		new Thread(task).start();
    	}
    }
    class Task implements Runnable{
    	public void run(){
    		synchronized(this){
    			System.out.println("吃饭...");
    			try {
    				Thread.sleep(2000);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    		synchronized(this){
    			System.out.println("看电视...");
    			try {
    				Thread.sleep(2000);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }

    结果:

    吃饭...
    看电视...
    吃饭...

    看电视...

    执行后会发现即使是不同的线程吃饭和看电视也是交替而不是同时执行的。这就是典型的互斥锁:当两个synchronized锁的对象(监视器)是同一个,修饰的代码片段不同时,那么这些代码片段所代表的任务是互斥的。

    三、死锁

    概念:当每个线程都持有自己的锁,但是都在请求对方释放锁时,就会造成死锁现象。

    案例:

    public class Test {
    	public static void main(String[] args) {
    		Task task = new Task();
    		Thread t1 = new Thread(){
    			public void run(){
    				task.methodA();
    			}
    		};
    		Thread t2 = new Thread(){
    			public void run(){
    				task.methodB();
    			}
    		};
    		
    		t1.start();
    		t2.start();
    	}
    }
    class Task{
    	private Object a = new Object();
    	private Object b = new Object();
    	public void run(){
    		
    	}
    	
    	public void methodA(){
    		System.out.println("开始执行A方法");
    		try {
    			Thread.sleep(2000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		synchronized(a){
    			System.out.println("对A方法上锁");
    			try {
    				Thread.sleep(2000);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			System.out.println("开始调用B方法");
    			methodB();
    			try {
    				Thread.sleep(2000);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    		System.out.println("A方法执行完毕");
    	}
    	public void methodB(){
    		System.out.println("开始执行B方法");
    		try {
    			Thread.sleep(2000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		synchronized(b){
    			System.out.println("对B方法上锁");
    			try {
    				Thread.sleep(2000);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			System.out.println("开始调用A方法");
    			methodA();
    			try {
    				Thread.sleep(2000);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    		System.out.println("B方法执行完毕");
    	}
    }

    结果:

    开始执行A方法
    开始执行B方法
    对A方法上锁
    对B方法上锁
    开始调用B方法
    开始执行B方法
    开始调用A方法

    开始执行A方法

    执行到这里后两个线程t1和t2会进入僵持状态,由于双方都是在持有自己锁的同时请求对方释放锁,因此这就形成了典型的死锁现象:这里是因为滥用锁对象造成的,大家可以将两个锁对象(监视器)换成同一个对象,程序就会向下继续运行,但是该程序继续向下运行并没有什么意义,这里只是为了说明死锁形成的简单场景。

    以上就是java里面典型的各种锁,有兴趣可以研究一下。

    展开全文
  • 浅谈Java锁机制

    万次阅读 多人点赞 2018-08-04 11:35:55
    这两天一直在准备面试,看了很多篇关于的介绍的博客,今天就总结一下。 首先需要知道几个名词: 公平/非公平 可重入 独享/共享 互斥/读写 乐观/悲观 分段 偏向/轻量级/重量级 ...

    这两天一直在准备面试,看了很多篇关于锁的介绍的博客,今天就总结一下。

    首先需要知道几个名词:

    • 公平锁/非公平锁
    • 可重入锁
    • 独享锁/共享锁
    • 互斥锁/读写锁
    • 乐观锁/悲观锁
    • 分段锁
    • 偏向锁/轻量级锁/重量级锁
    • 自旋锁

    公平锁/非公平锁:

    公平锁是指多个线程按照申请锁的顺序来获取锁。
    非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。

    可重入锁:

    可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。

    // 此处用代码演示了可重入锁的代码层意思
    synchronized void setA() throws Exception{   
        Thread.sleep(1000);
        setB();   // 因为获取了setA()的锁(即获取了方法外层的锁)、此时调用setB()将会自动获取setB()的锁,如果不自动获取的话方法B将不会执行           
    }
    
    synchronized void setB() throws Exception{
        Thread.sleep(1000);
    }

    独享锁/共享锁:

    独享锁是指该锁一次只能被一个线程所持有。
    共享锁是指该锁可被多个线程所持有。

    互斥锁/读写锁

    上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。

    乐观锁/悲观锁

    乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。
    悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。
    乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。

    从上面的描述我们可以看出,悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升。
    悲观锁在Java中的使用,就是利用各种锁。
    乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新。

    重量级锁是悲观锁的一种,自旋锁、轻量级锁与偏向锁属于乐观锁

    分段锁

    分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。
    我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。
    当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。
    但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。
    分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。

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

    这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。
    偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。

    偏向锁的适用场景

    始终只有一个线程在执行同步块,在它没有执行完释放锁之前,没有其它线程去执行同步块,在锁无竞争的情况下使用,一旦有了竞争就升级为轻量级锁,升级为轻量级锁的时候需要撤销偏向锁,撤销偏向锁的时候会导致stop the word操作; 
    在有锁的竞争时,偏向锁会多做很多额外操作,尤其是撤销偏向所的时候会导致进入安全点,安全点会导致stw,导致性能下降,这种情况下应当禁用;

    轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
    重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

    自旋锁

    在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

    自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗

    自旋锁尽可能的减少线程的阻塞,适用于锁的竞争不激烈,且占用锁时间非常短的代码块来说性能能大幅度的提升,因为自旋的消耗会小于线程阻塞挂起再唤醒的操作的消耗

    但是如果锁的竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,这时候就不适合使用自旋锁了,因为自旋锁在获取锁前一直都是占用cpu做无用功,同时有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,其它需要cpu的线程又不能获取到cpu,造成cpu的浪费。

    熟悉几个概念以后我们开始详细说一下java中的锁:

    我们所熟知的Java锁机制无非就是Sychornized 锁 和 Lock锁

    Synchronized是基于JVM来保证数据同步的,而Lock则是在硬件层面,依赖特殊的CPU指令实现数据同步的

    • Synchronized,它就是一个:非公平,悲观,独享,互斥,可重入的重量级锁
    • ReentrantLock,它是一个:默认非公平但可实现公平的,悲观,独享,互斥,可重入,重量级锁。
    • ReentrantReadWriteLocK,它是一个,默认非公平但可实现公平的,悲观,写独享,读共享,读写,可重入,重量级锁。

    Synchronized的作用:

    在JDK1.5之前都是使用synchronized关键字保证同步的

    它可以把任意一个非NULL的对象当作锁。

    1. 作用于方法时,锁住的是对象的实例(this);
    2. 当作用于静态方法时,锁住的是Class实例,又因为Class的相关数据存储在永久带PermGen(jdk1.8则是metaspace),永久带是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁所有调用该方法的线程;
    3. synchronized作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。

    Synchronized的实现:

    实现如下图所示;

    这里写图片描述

    它有多个队列,当多个线程一起访问某个对象监视器的时候,对象监视器会将这些线程存储在不同的容器中。

    1. Contention List:竞争队列,所有请求锁的线程首先被放在这个竞争队列中;

    2. Entry List:Contention List中那些有资格成为候选资源的线程被移动到Entry List中;

    3. Wait Set:哪些调用wait方法被阻塞的线程被放置在这里;

    4. OnDeck:任意时刻,最多只有一个线程正在竞争锁资源,该线程被成为OnDeck;

    5. Owner:当前已经获取到所资源的线程被称为Owner;

    6. !Owner:当前释放锁的线程。

    ContentionList并不是真正意义上的一个队列。仅仅是一个虚拟队列,它只有Node以及对应的Next指针构成,并没有Queue的数据结构。每次新加入Node会在队头进行,通过CAS改变第一个节点为新增节点,同时新增阶段的next指向后续节点,而取数据都在队列尾部进行。

    JVM每次从队列的尾部取出一个数据用于锁竞争候选者(OnDeck),但是并发情况下,ContentionList会被大量的并发线程进行CAS访问,为了降低对尾部元素的竞争,JVM会将一部分线程移动到EntryList中作为候选竞争线程。Owner线程会在unlock时,将ContentionList中的部分线程迁移到EntryList中,并指定EntryList中的某个线程为OnDeck线程(一般是最先进去的那个线程)。Owner线程并不直接把锁传递给OnDeck线程,而是把锁竞争的权利交给OnDeck,OnDeck需要重新竞争锁。这样虽然牺牲了一些公平性,但是能极大的提升系统的吞吐量,在JVM中,也把这种选择行为称之为“竞争切换”。

    OnDeck线程获取到锁资源后会变为Owner线程,而没有得到锁资源的仍然停留在EntryList中。如果Owner线程被wait方法阻塞,则转移到WaitSet队列中,直到某个时刻通过notify或者notifyAll唤醒,会重新进去EntryList中。

    处于ContentionList、EntryList、WaitSet中的线程都处于阻塞状态,该阻塞是由操作系统来完成的(Linux内核下采用pthread_mutex_lock内核函数实现的)。该线程被阻塞后则进入内核调度状态,会导致系统在用户和内核之间进行来回切换,严重影响锁的性能。为了缓解上述性能问题,JVM引入了自旋锁(上边已经介绍过自旋锁)。原理非常简单,如果Owner线程能在很短时间内释放锁资源,那么哪些等待竞争锁的线程可以稍微等一等(自旋)而不是立即阻塞,当Owner线程释放锁后可立即获取锁,进而避免用户线程和内核的切换。但是Owner可能执行的时间会超过设定的阈值,争用线程在一定时间内还是获取不到锁,这是争用线程会停止自旋进入阻塞状态。基本思路就是先自旋等待一段时间看能否成功获取,如果不成功再执行阻塞,尽可能的减少阻塞的可能性,这对于占用锁时间比较短的代码块来说性能能大幅度的提升!

    Synchronized在线程进入ContentionList时,等待的线程会先尝试自旋获取锁,如果获取不到就进入ContentionList,这明显对于已经进入队列的线程是不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占OnDeck线程的锁资源。

    Lock锁简介:

    与synchronized不同的是,Lock锁是纯Java实现的,与底层的JVM无关。在java.util.concurrent.locks包中有很多Lock的实现类,常用的有ReentrantLock、ReadWriteLock(实现类ReentrantReadWriteLock),其实现都依赖java.util.concurrent.AbstractQueuedSynchronizer类(简称AQS)

    Lock的实现:

    获取锁:首先判断当前状态是否允许获取锁,如果是就获取锁,否则就阻塞操作或者获取失败,也就是说如果是独占锁就可能阻塞,如果是共享锁就可能失败。另外如果是阻塞线程,那么线程就需要进入阻塞队列。当状态位允许获取锁时就修改状态,并且如果进了队列就从队列中移除。

    while (synchronization state does not allow acquire) {
    
        enqueue current thread if not already queued;
    
        possibly block current thread;
    
    }
    
    dequeue current thread if it was queued;

    释放锁:这个过程就是修改状态位,如果有线程因为状态位阻塞的话,就唤醒队列中的一个或者更多线程。

    update synchronization state;
    
    if (state may permit a blocked thread to acquire)
    
        unlock one or more queued threads;

      阻塞和唤醒,JDK1.5之前的API中并没有阻塞一个线程,然后在将来的某个时刻唤醒它(wait/notify是基于synchronized下才生效的,在这里不算),JDK5之后利用JNI在LockSupport 这个类中实现了相关的特性!

    3、  有序队列:在AQS中采用CLH队列来解决队列的有序问题。

    我们来看下ReentrantLock的调用过程

    经过源码分析,我们看到ReentrantLock把所有的Lock都委托给Sync类进行处理,该类继承自AQS,其类关系图如下

    其中Sync又有两个final static的子类NonfairSync和FairSync用于支持非公平锁和公平锁。我们先来挑一个看下对应Reentrant.lock()的调用过程(默认为非公平锁)

    这些模版很难让我们直观的看到整个调用过程,但是通过上面的过程图和AbstractQueuedSynchronizer的注释可以看出,AbstractQueuedSynchronizer抽象了大多数Lock的功能,而只把tryAcquire(int)委托给子类进行多态实现。tryAcquire用于判断对应线程事都能够获取锁,无论成功与否,AbstractQueuedSynchronizer都将处理后面的流程。

    简单来讲,AQS会把所有请求锁的线程组成一个CLH的队列,当一个线程执行完毕释放锁(Lock.unlock())的时候,AQS会激活其后继节点,正在执行的线程不在队列当中,而那些等待的线程全部处于阻塞状态,经过源码分析,我们可以清楚的看到最终是通过LockSupport.park()实现的,而底层是调用sun.misc.Unsafe.park()本地方法,再进一步,HotSpot在Linux中中通过调用pthread_mutex_lock函数把线程交给系统内核进行阻塞。其运行示意图如下

    与synchronized相同的是,这个也是一个虚拟队列,并不存在真正的队列示例,仅存在节点之前的前后关系。(注:原生的CLH队列用于自旋锁,JUC将其改造为阻塞锁)。和synchronized还有一点相同的是,就是当获取锁失败的时候,不是立即进行阻塞,而是先自旋一段时间看是否能获取锁,这对那些已经在阻塞队列里面的线程显然不公平(非公平锁的实现,公平锁通过有序队列强制线程顺序进行),但会极大的提升吞吐量。如果自旋还是获取失败了,则创建一个节点加入队列尾部,加入方法仍采用CAS操作,并发对队尾CAS操作有可能会发生失败,AQS是采用自旋循环的方法,知道CAS成功!下面我们来看下锁的实现细节!

    锁的实现依赖与lock()方法,Lock()方法首先是调用acquire(int)方法,不管是公平锁还是非公平锁

    public final void acquire(int arg) {
             if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                 selfInterrupt();
    }

    Acquire()方法默认首先调用tryAcquire(int)方法,而此时公平锁和不公平锁的实现就不一样了。

    1、Sync.NonfairSync.TryAcquire(非公平锁)

    nonfairTryAcquire方法是lock方法间接调用的第一个方法,每次调用都会首先调用这个方法,我们来看下对应的实现代码:

    final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

    该方法首先会判断当前线程的状态,如果c==0 说明没有线程正在竞争锁。(反过来,如果c!=0则说明已经有其他线程已经拥有了锁)。如果c==0,则通过CAS将状态设置为acquires(独占锁的acquires为1),后续每次重入该锁都会+1,每次unlock都会-1,当数据为0时则释放锁资源。其中精妙的部分在于:并发访问时,有可能多个线程同时检测到c为0,此时执行compareAndSetState(0, acquires))设置,可以预见,如果当前线程CAS成功,则其他线程都不会再成功,也就默认当前线程获取了锁,直接作为running线程,很显然这个线程并没有进入等待队列。如果c!=0,首先判断获取锁的线程是不是当前线程,如果是当前线程,则表明为锁重入,继续+1,修改state的状态,此时并没有锁竞争,也非CAS,因此这段代码也非常漂亮的实现了偏向锁。

    想知道Lock和sychornized的区别的可以看这一篇博客

    参考的博客有:

    https://www.cnblogs.com/longshiyVip/p/5213771.html

    https://blog.csdn.net/zqz_zqz/article/details/70233767

     

     

     

     

     

     

     

     

     

     

    展开全文
  • 从并发概念、场景分析出发,依次引出锁、等待队列等概念,直至分析清楚java锁机制实现的原理。并以java锁机制实现基类AbstractQueuedSynchronizer的实现为例,从类(核心属性、方法)设计思路,到对关键代码做注释...
  • Java 锁机制

    千次阅读 2018-07-21 17:37:50
    当没有竞争的时候,系统会默认使用偏斜。JVM 利用CAS(compare and swap)在 对象头的第一部分(mark word)设置 偏向线程ID,表示对象偏向于这个线程。 因为大部分并发场景下面 对象 生命周期 中最多被一个线程...

    当没有竞争的时候,系统会默认使用偏斜锁。JVM 利用CAS(compare and swap)在 对象头的第一部分(mark word)设置 偏向线程ID,表示对象偏向于这个线程。 因为大部分并发场景下面 对象 生命周期 中最多被一个线程锁定, 使用偏斜锁的话可以降低 低竞争状态下的 额外开销。

    这个时候如果有另外的线程试图锁定已经被倾斜过的对象,JVM会撤销(revoke)偏斜锁, 并切换到轻量级锁。 这时候轻量级锁 会根据 CAS 操作 Mark word 来试图 获取锁,如果成功就 切换为轻量级锁;否则 进一步 升级 为重量级锁。

     

    自旋锁: 是竞争失败的线程,并不会真实的在操作系统层面挂起等待,而是JVM会让线程做几次空循环(For (;;)),循环几轮后,如果可以获得锁,那么进入临界区,如果还是不能获得锁的话,这时候才会在操作系统挂起等待。 一般在 低竞争并且占用锁的时间短的线程很实用。是一种在悲观锁中的乐观优化(因为JVM的synchronized是一种悲观锁 )。

     

    死锁: 因为悲观锁独占CPU的特性,死锁就是线程A在持有锁A的情况下,试图进入锁B; 而线程B 在持有 锁B的情况下,试图进入锁A。如下图:

    例如多个嵌套的 synchronized 有可能会发生死锁的情况。 所以为了避免死锁, 我们就需要 注意锁的获取顺序。 或者使用Object.wait() 或者  CountDownLatch.await()等带超时的方法。在长时间没有锁的情况下,即退出逻辑。

    另外如果发生死锁的情况下可以使用jstack等工具去获取死锁发生的位置,这里就不过多阐述。

    展开全文
  • java锁机制Synchronized.pdf
  • java锁机制的面试题

    千次阅读 2020-04-18 16:10:52
    synchronized 和 lock 机制区别 synchronized 原始采用的是 CPU 悲观锁机制,即线程获得的是独占锁。独占锁意味着其 他线程只能依靠阻塞来等待线程释放锁。 Lock 用的是乐观锁方式。所谓乐观锁就是,每次不加锁...

    1、ABA问题

    CAS 会导致“ABA问题”。

    CAS 算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化。

    比如说一个线程 one 从内存位置 V 中取出 A,这时候另一个线程 two 也从内存中取出 A,并且 two 进行了一些操作变成了 B,然后 two 又将 V 位置的数据变成 A,这时候线程 one 进行 CAS 操作发现内存中仍然是 A,然后 one 操作成功。尽管线程 one 的 CAS 操作成功,但是不代表这个过程就是没有问题的。

    部分乐观锁的实现是通过版本号(version)的方式来解决 ABA 问题,乐观锁每次在执行数据的修改操作时,都会带上一个版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行 +1 操作,否则就执行失败。因为每次操作的版本号都会随之增加,所以不会出现 ABA 问题,因为版本号只会增加不会减少。

    2、CAS乐观锁

    CAS 是项乐观锁技术,当多个线程尝试使用 CAS 同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

    CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”这其实和乐观锁的冲突检查 + 数据更新的原理是一样的。

    3、synchronize实现原理

    1同步代码块是使用 monitorenter 和 monitorexit 指令实现的,同步方法(在这看不出来需要看 JVM 底层实现)依靠的是方法修饰符上的 ACC_SYNCHRONIZED 实现。

    4、synchronize与lock的区别

    synchronized 和 lock 的用法区别

     

    osynchronized(隐式锁):在需要同步的对象中加入此控制,synchronized 可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。

    olock(显示锁):需要显示指定起始位置和终止位置。一般使用 ReentrantLock 类做为锁,多个线程中必须要使用一个 ReentrantLock 类做为对象才能保证锁的生效。且在加锁和解锁处需要通过 lock() 和 unlock() 显示指出。所以一般会在 finally块中写 unlock() 以防死锁。

     

    synchronized 和 lock 性能区别 synchronized 是托管给 JVM 执行的,而 lock是 Java 写的控制锁的代码。在 JDK 1.5 中,synchronize 是性能低效的。因为这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用 Java 提供的 Lock 对象,性能更高一些。但是到了 JDK 1.6,发生了变化。synchronize 在语义上很清晰,可以进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在 JDK 1.6 上 synchronize 的性能并不比 Lock 差。

    synchronized 和 lock 机制区别

    synchronized 原始采用的是 CPU 悲观锁机制,即线程获得的是独占锁。独占锁意味着其 他线程只能依靠阻塞来等待线程释放锁。

    Lock 用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是 CAS 操作(Compare and Swap)。

    5、volatile实现原理

    在 JVM 底层 volatile 是采用“内存屏障”来实现的

    缓存一致性协议(MESI协议)它确保每个缓存中使用的共享变量的副本是一致的。其核心思想如下:当某个 CPU 在写数据时,如果发现操作的变量是共享变量,则会通知其他 CPU 告知该变量的缓存行是无效的,因此其他 CPU 在读取该变量时,发现其无效会重新从主存中加载数据

    6、乐观锁的业务场景及实现方式

    乐观锁(Optimistic Lock):

    每次获取数据的时候,都不会担心数据被修改,所以每次获取数据的时候都不会进行加锁,但是在更新数据的时候需要判断该数据是否被别人修改过。如果数据被其他线程修改,则不进行数据更新,如果数据没有被其他线程修改,则进行数据更新。由于数据没有进行加锁,期间该数据可以被其他线程进行读写操作。

    比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。

    7、说说线程安全的问题

    线程安全是多线程领域的问题,线程安全可以简单理解为一个方法或者一个实例可以在多线程环境中使用而不会出现问题。

    在 Java 多线程编程当中,提供了多种实现 Java 线程安全的方式:

    最简单的方式,使用 Synchronization 关键字

    使用 java.util.concurrent.atomic 包中的原子类,例如 AtomicInteger

    使用 java.util.concurrent.locks 包中的锁

    使用线程安全的集合 ConcurrentHashMap

    使用 volatile关键字,保证变量可见性(直接从内存读,而不是从线程 cache 读)

    展开全文
  • 详解Java锁机制:看完你就明白的锁系列之锁的状态 前言: 看完你就会知道,线程如果锁住了某个资源,致使其他线程无法访问的这种锁被称为悲观锁,相反,线程不锁住资源的锁被称为乐观锁,而自旋锁是基于 CAS 机制...
  • java锁机制详解.pdf

    2021-10-04 00:22:32
    java锁机制详解.pdf
  • 关于Java锁机制面试官会怎么问

    千次阅读 2018-07-09 13:39:49
    传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比如Java里面的同步原语synchronized关键字的实现也是悲观锁。乐观锁:顾名思义,就是很乐观,每次去拿...
  • java锁机制Synchronized java锁机制Synchronized java锁机制Synchronized java锁机制Synchronized
  • 面向Java锁机制的字节码自动重构框架.pdf
  • java锁机制Synchronized[归纳].pdf
  • JAVA锁机制

    千次阅读 2017-01-25 17:26:19
    synchronized和Lock均为可重入。即可为该对象多次加锁,通过标志+1进行操作;当所标志为0时,释放所。重入目的是为防止死锁发生。 synchronized:基于系统内核实现线程等待(通过linux系统pthread_...
  • 面向Java锁机制的字节码自动重构框架
  • 深入解析Java锁机制

    千次阅读 2018-11-20 08:48:58
    synchronized是悲观,在操作同步资源之前需要给同步资源先加锁,这把就是存在Java对象头里的,而Java对象头又是什么呢? 我们以Hotspot虚拟机为例,Hotspot的对象头主要包括两部分数据:Mark Word(标记字段...
  • Java锁机制(一)synchronized

    千次阅读 2021-02-28 12:50:24
    Java中可以使用一些手段来达到线程同步的目的:1. synchronized2. ThreadLocal,线程本地变量3. Java.util.concurrent.LockJava中,线程会共享堆上的实例变量以及方法区的类变量,而栈上的数据是私有的,不必进行...
  • 在计算机科学中,(lock)或互斥(mutex)是一种同步机制,用于在有许多执行线程的环境中强制对资源的访问限制。旨在强制实施互斥排他、并发控制策略。 通常需要硬件支持才能有效实施。这种支持通常采取一个或多...
  • 面试必会系列 - 1.5 Java 锁机制

    万次阅读 2020-09-04 12:25:48
    Java 锁机制 概览 syncronized 锁升级过程 ReentrantLock 可重入锁 volatile 关键字 JUC 包下新的同步机制 syncronized 给一个变量/一段代码加锁,线程拿到锁之后,才能修改一个变量/执行一段代码 wait() notify...
  • 一篇文章即可读懂java中的锁机制

    万次阅读 多人点赞 2019-03-27 09:45:16
    java中的分为以下(其实就是按照的特性和设计来划分): 1、公平/非公平 2、可重入 3、独享/共享 4、互斥/读写 5、乐观/悲观 6、分段 7、偏向/轻量级/重量级 8、自旋...
  • Java锁性能提高(锁升级)机制总结

    千次阅读 多人点赞 2017-03-21 11:38:37
    的使用很难避免,如何尽量提高的性能就显得比较重要了 偏向 所谓的偏向是指在对象实例的Mark Word(说白了就是对象内存中的开头几个字节保留的信息,如果把一个对象序列化后明显可以看见开头的这些信息)...
  • 主要介绍了Java多线程锁机制相关原理实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • Java中常用的锁机制

    万次阅读 多人点赞 2018-03-29 23:43:10
    在计算机科学中,(lock)或互斥(mutex)是一种同步机制,用于在有许多执行线程的环境中强制对资源的访问限制。旨在强制实施互斥排他、并发控制策略。 通常需要硬件支持才能有效实施。这种支持通常采取一个或...
  • Java并发机制的实现原理

    万次阅读 多人点赞 2016-07-18 20:04:21
    Java并发机制实现原理

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 269,741
精华内容 107,896
关键字:

java锁机制

java 订阅
友情链接: 943456.zip