精华内容
下载资源
问答
  • java锁的底层实现

    2019-03-11 11:42:50
    [https://blog.csdn.net/qq_29753285/article/details/81299509]...:synchronized 和 reentrantlock 一、synchronized 1、CAS(compare and swap) 为了提高性能,...

    https://blog.csdn.net/qq_29753285/article/details/81299509

    锁:synchronized 和 reentrantlock
    一、synchronized
    1、CAS(compare and swap)
    为了提高性能,JVM很多操作都依赖CAS实现,一种乐观锁的实现。本文锁优化中大量用到了CAS,故有必要先分析一下CAS的实现。
    CAS:Compare and Swap。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。
    否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。
    CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。如果A=V,那么把B赋值给V,返回V;如果A!=V,直接返回V。
    2. CAS的目的
    利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。而整个J.U.C都是建立在CAS之上的,
    因此对于synchronized阻塞算法,J.U.C在性能上有了很大的提升。
    3. CAS存在的问题
    CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题。ABA问题,循环时间长开销大和只能保证一个共享变量的原子操作

    Java中的Monitor
    java会为每个object对象分配一个monitor,当某个对象的同步方法(synchronized methods )被多个线程调用时,
    该对象的monitor将负责处理这些访问的并发独占要求。

    当一个线程调用一个对象的同步方法时,JVM会检查该对象的monitor。如果monitor没有被占用,那么这个线程就得到了monitor的占有权,可以继续执行该对象的同步方法;
    如果monitor被其他线程所占用,那么该线程将被挂起,直到monitor被释放。

     locked_value = 0,//00偏向锁 
     unlocked_value = 1,//01无锁 
     monitor_value = 2,//10监视器锁,又叫重量级锁 
     marked_value = 3,//11GC标记 
     biased_lock_pattern = 5//101偏向锁
    

    synchronized关键字修饰的代码段,在JVM被编译为monitorenter、monitorexit指令来获取和释放互斥锁。

    monitorenter	:获取锁
    monitorexit	:判断锁是否存在
    

    偏向锁的获取 ;fast_enter(); 再fast_enter()方法中判断是否开启了偏向锁,如果未开启则调用非偏向锁:slow_enter();

    偏向锁这个函数定义了获取偏向锁的流程,具体逻辑总结:
    1. 判断当前对象是否为可偏向(101),且偏向时间戳已过期(没有其他线程在占用该对象),如果是,则进入步骤2,否则进入步骤3
    2. 执行CAS操作将markword中的线程ID替换为本线程ID。如果成功则进入步骤4,否则进入步骤3
    3. 存在竞争,当达到全局安全点(safepoint),获得偏向锁的线程被挂起,撤销偏向锁,并升级为轻量级,升级完成后被阻塞在安全点的线程继续执行同步代码块;
    4. 执行同步代码

    偏向锁的撤销 : revoke_at_safepoint
    只有当其它线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,偏向锁的撤销由BiasedLocking::revoke_at_safepoint方法实现:

    偏向锁的释放,需要等待全局安全点(在这个时间点上没有正在执行的字节码),它会首先暂停拥有偏向锁的线程,
    然后检查持有偏向锁的线程是否还活着,如果线程不处于活动状态,则将对象头设置成无锁状态。如果线程仍然活着,
    拥有偏向锁的栈会被执行,遍历偏向对象的所记录。栈帧中的锁记录和对象头的Mark Word要么重新偏向其他线程,要么恢复到无锁,
    或者标记对象不适合作为偏向锁。最后唤醒暂停的线程。

    偏向锁在Java 1.6之后是默认启用的,但在应用程序启动几秒钟之后才激活,可以使用-XX:BiasedLockingStartupDelay=0参数关闭延迟,
    如果确定应用程序中所有锁通常情况下处于竞争状态,可以通过XX:-UseBiasedLocking=false参数关闭偏向锁

    轻量级锁的获取:slow_entry
    如果关闭了偏向锁功能,则会直接再monitorenter方法中进入slow_enter。否则,在faster_enter中竞争偏向锁导致偏向锁升级为轻量级锁后进入。

    轻量级锁的释放:fast_exit
    1、确保处于偏向锁状态时不会执行这段逻辑;
    2、取出在获取轻量级锁时保存在BasicLock对象的mark数据dhw;
    3、通过CAS尝试把dhw替换到当前的Mark Word,如果CAS成功,说明成功的释放了锁,否则执行步骤(4);
    4、如果CAS失败,说明有其它线程在尝试获取该锁,这时需要将该锁升级为重量级锁,并释放;

    重量级锁的 ;
    重量级锁的膨胀过程:ObjectSynchronizer::inflate
    膨胀过程的实现比较复杂,大概实现过程如下:
    1、整个膨胀过程在自旋下完成;
    2、mark->has_monitor()方法判断当前是否为重量级锁(上图18-25行),即Mark Word的锁标识位为 10,如果当前状态为重量级锁,执行步骤(3),否则执行步骤(4);
    3、膨胀过程已经完成:mark->monitor()方法获取指向ObjectMonitor的指针,并返回。
    4、当前锁处于膨胀中(上图33-37行),说明该锁正在被其它线程执行膨胀操作,则当前线程就进行自旋等待锁膨胀完成,
    这里需要注意一点,虽然是自旋操作,但不会一直占用cpu资源,每隔一段时间会通过os::NakedYield方法放弃cpu资源,或通过park方法挂起;如果其他线程完成锁的膨胀操作,则退出自旋并返回;
    5、当前是轻量级锁状态(上图58-138行),即锁标识位为 00,膨胀过程如下:
    通过omAlloc方法,获取一个可用的ObjectMonitor monitor,并重置monitor数据;
    通过CAS尝试将Mark Word设置为markOopDesc:INFLATING,标识当前锁正在膨胀中,如果CAS失败,说明同一时刻其它线程已经将Mark Word设置为markOopDesc:INFLATING,当前线程进行自旋等待膨胀完成;
    如果CAS成功,设置monitor的各个字段:_header、_owner和_object等,并返回;
    6、无锁(中立,上图150-186行),重置监视器值;

    重量级锁的竞争:ObjectMonitor::enter
    1、通过CAS尝试把monitor的_owner字段设置为当前线程;
    2、如果设置之前的_owner指向当前线程,说明当前线程再次进入monitor,即重入锁,执行_recursions ++ ,记录重入的次数;
    3、如果之前的_owner指向的地址在当前线程中,这种描述有点拗口,换一种说法:之前_owner指向的BasicLock在当前线程栈上,说明当前线程是第一次进入该monitor,设置_recursions为1,_owner为当前线程,该线程成功获得锁并返回;
    4、如果获取锁失败,则等待锁的释放;

    monitor等待
    monitor竞争失败的线程,通过自旋执行ObjectMonitor::EnterI方法等待锁的释放,EnterI方法的部分逻辑实现如下:
    1、当前线程被封装成ObjectWaiter对象node,状态设置成ObjectWaiter::TS_CXQ;
    2、在for循环中,通过CAS把node节点push到_cxq列表中,同一时刻可能有多个线程把自己的node节点push到_cxq列表中;
    3、node节点push到_cxq列表之后,通过自旋尝试获取锁,如果还是没有获取到锁,则通过park将当前线程挂起,等待被唤醒,实现如下:
    4、当该线程被唤醒时,会从挂起的点继续执行,通过ObjectMonitor::TryLock尝试获取锁,TryLock方法实现如下:

    monitor释放
    当某个持有锁的线程执行完同步代码块时,会进行锁的释放,给其它线程机会执行同步代码,在HotSpot中,
    通过退出monitor的方式实现锁的释放,并通知被阻塞的线程,具体实现位于ObjectMonitor::exit方法中。

    二、锁Lock
    https://blog.csdn.net/u011109589/article/details/80242931
    经过观察ReentrantLock把所有Lock接口的操作都委派到一个Sync类上,该类继承了AbstractQueuedSynchronizer
    锁实现(加锁)

    简单说来,AbstractQueuedSynchronizer会把所有的请求线程构成一个CLH队列,当一个线程执行完毕(lock.unlock())时会激活自己的后继节点,
    但正在执行的线程并不在队列中,而那些等待执行的线程全部处于阻塞状态,经过调查线程的显式阻塞是通过调用LockSupport.park()完成,
    而LockSupport.park()则调用sun.misc.Unsafe.park()本地方法,再进一步,HotSpot在Linux中中通过调用pthread_mutex_lock函数把线程交给系统内核进行阻塞。

    2.1 Sync.nonfairTryAcquire
    nonfairTryAcquire方法将是lock方法间接调用的第一个方法,每次请求锁时都会首先调用该方法。
    该方法会首先判断当前状态,如果c0说明没有线程正在竞争该锁,如果不c !=0 说明有线程正拥有了该锁。
    如果发现c
    0,则通过CAS设置该状态值为acquires,acquires的初始调用值为1,每次线程重入该锁都会+1,每次unlock都会-1,但为0时释放锁。
    如果CAS设置成功,则可以预计其他任何线程调用CAS都不会再成功,也就认为当前线程得到了该锁,也作为Running线程,很显然这个Running线程并未进入等待队列。
    如果c !=0 但发现自己已经拥有锁,只是简单地++acquires,并修改status值,但因为没有竞争,所以通过setStatus修改,而非CAS,
    也就是说这段代码实现了偏向锁的功能,并且实现的非常漂亮

    	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;  
    	}  
    	
    2.2 AbstractQueuedSynchronizer.addWaiter	
    	addWaiter方法负责把当前无法获得锁的线程包装为一个Node添加到队尾:
    	private Node addWaiter(Node mode) {  
    		Node node = new Node(Thread.currentThread(), mode);  
    		// Try the fast path of enq; backup to full enq on failure  
    		Node pred = tail;  
    		if (pred != null) {  
    			node.prev = pred;  
    			if (compareAndSetTail(pred, node)) {  
    				pred.next = node;  
    				return node;  
    			}  
    		}  
    		enq(node);  
    		return node;  
    	}  
    

    其中参数mode是独占锁还是共享锁,默认为null,独占锁。追加到队尾的动作分两步:
    如果当前队尾已经存在(tail!=null),则使用CAS把当前线程更新为Tail
    如果当前Tail为null或则线程调用CAS设置队尾失败,则通过enq方法继续设置Tail

    	下面是enq方法:
    	    private Node enq(final Node node) {  
    			for (;;) {  
    				Node t = tail;  
    				if (t == null) { // Must initialize  
    					Node h = new Node(); // Dummy header  
    					h.next = node;  
    					node.prev = h;  
    					if (compareAndSetHead(h)) {  
    						tail = node;  
    						return h;  
    					}  
    				}  
    				else {  
    					node.prev = t;  
    					if (compareAndSetTail(t, node)) {  
    						t.next = node;  
    						return t;  
    					}  
    				}  
    			}  
    		}  
    

    该方法就是循环调用CAS,即使有高并发的场景,无限循环将会最终成功把当前线程追加到队尾(或设置队头)。
    总而言之,addWaiter的目的就是通过CAS把当前线程追加到队尾,并返回包装后的Node实例。

    把线程要包装为Node对象的主要原因,除了用Node构造供虚拟队列外,还用Node包装了各种线程状态,这些状态被精心设计为一些数字值:
    SIGNAL(-1) :线程的后继线程正/已被阻塞,当该线程release或cancel时要重新这个后继线程(unpark)
    CANCELLED(1):因为超时或中断,该线程已经被取消
    CONDITION(-2):表明该线程被处于条件队列,就是因为调用了Condition.await而被阻塞
    PROPAGATE(-3):传播共享锁
    0:0代表无状态

    2.3 AbstractQueuedSynchronizer.acquireQueued
    acquireQueued的主要作用是把已经追加到队列的线程节点(addWaiter方法返回值)进行阻塞,但阻塞前又通过tryAccquire重试是否能获得锁,
    如果重试成功能则无需阻塞,直接返回

        final boolean acquireQueued(final Node node, int arg) {  
    		try {  
    			boolean interrupted = false;  
    			for (;;) {  
    				final Node p = node.predecessor();  
    				if (p == head && tryAcquire(arg)) {  
    					setHead(node);  
    					p.next = null; // help GC  
    					return interrupted;  
    				}  
    				if (shouldParkAfterFailedAcquire(p, node) &&  
    					parkAndCheckInterrupt())  
    					interrupted = true;  
    			}  
    		} catch (RuntimeException ex) {  
    			cancelAcquire(node);  
    			throw ex;  
    		}  
    	}  
    

    仔细看看这个方法是个无限循环,感觉如果p == head && tryAcquire(arg)条件不满足循环将永远无法结束,当然不会出现死循环,
    奥秘在于第12行的parkAndCheckInterrupt会把当前线程挂起,从而阻塞住线程的调用栈。

    	private final boolean parkAndCheckInterrupt() {  
    		LockSupport.park(this);  
    		return Thread.interrupted();  
    	}  
    

    如前面所述,LockSupport.park最终把线程交给系统(Linux)内核进行阻塞。当然也不是马上把请求不到锁的线程进行阻塞,
    还要检查该线程的状态,比如如果该线程处于Cancel状态则没有必要,具体的检查在shouldParkAfterFailedAcquire中:

     private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {  
    			  int ws = pred.waitStatus;  
    			  if (ws == Node.SIGNAL)  
    				  /* 
    				   * This node has already set status asking a release 
    				   * to signal it, so it can safely park 
    				   */  
    				  return true;  
    			  if (ws > 0) {  
    				  /* 
    				   * Predecessor was cancelled. Skip over predecessors and 
    				   * indicate retry. 
    				   */  
    		   do {  
    					node.prev = pred = pred.prev;  
    			} while (pred.waitStatus > 0);  
    					pred.next = node;  
    			} else {  
    				  /* 
    				   * waitStatus must be 0 or PROPAGATE. Indicate that we 
    				   * need a signal, but don't park yet. Caller will need to 
    				   * retry to make sure it cannot acquire before parking.  
    				   */  
    				  compareAndSetWaitStatus(pred, ws, Node.SIGNAL);  
    			  }   
    			  return false;  
    		  }  
    

    检查原则在于:
    规则1:如果前继的节点状态为SIGNAL,表明当前节点需要unpark,则返回成功,此时acquireQueued方法的第12行(parkAndCheckInterrupt)将导致线程阻塞
    规则2:如果前继节点状态为CANCELLED(ws>0),说明前置节点已经被放弃,则回溯到一个非取消的前继节点,返回false,acquireQueued方法的无限循环将递归调用该方法,直至规则1返回true,导致线程阻塞
    规则3:如果前继节点状态为非SIGNAL、非CANCELLED,则设置前继的状态为SIGNAL,返回false后进入acquireQueued的无限循环,与规则2同
    总体看来,shouldParkAfterFailedAcquire就是靠前继节点判断当前线程是否应该被阻塞,如果前继节点处于CANCELLED状态,则顺便删除这些节点重新构造队列。

    展开全文
  • 获得偏向锁的线程,在对象头中写有线程id,不会主动释放锁: 没有其他线程竞争,对象头中始终是获得锁的tid。 线程tid2竞争,此时objhead中仍存着tid1。JVM首先安全暂停tid1,判断tid1是否仍处于同步块,如果是,...

    偏向锁

    JVM默认开启偏向锁。在对象头中锁标志前一位用来标记偏向锁是否可用。
    锁标记01表示偏向锁。

    偏向锁释放

    获得偏向锁的线程,在对象头中写有线程id,不会主动释放锁:

    • 没有其他线程竞争,对象头中始终是获得锁的tid。
    • 线程tid2竞争,此时objhead中仍存着tid1。JVM首先安全暂停tid1,判断tid1是否仍处于同步块,如果是,那么需要升级锁;如果不是,说明tid1不需要锁了,objhead中的tid字段置零——释放锁。线程可以再次竞争偏向锁。

    偏向锁就是这个意思,偏向已经获取到锁的线程,默认不释放,只有竞争发生时,才有释放锁的可能,如果此时仍然需要锁定,那么竞争失败,可以认定线程竞争比较频繁,因此,需要升级锁。

    CAS

    CAS是获取锁的关键操作,大概是boolean CAS(objhead.tid,markword,selftid_markword)这样的方法,其中markword是未加锁状态的markword,主要是测试锁标记为01时,objhead是否无锁(tid==0),如果是,则设置自己的tid,表明加锁。
    锁的本质是一个状态量,竞争锁的关键点在于检查状态和设置状态是原子完成的。

    轻量级锁

    自旋5000次

    占用锁的线程在同步代码结束后,使用cas释放锁,当有竞争时,此时的锁标记已经被竞争者修改为重量级锁10,cas操作失败,有锁线程继续按照新的锁标记释放锁,此时多线程按照重量级锁竞争。

    重量级锁

    使用OS的Mutex Lock

    转载于:https://www.cnblogs.com/linlei2099/p/8982573.html

    展开全文
  • AQS是一个构建锁的框架。 - 2. AQS中的数据结构 - 3. AQS设计模式 - 4. 从ReentrantLock的加锁过程来理解AQS 概述 重要引用 ''' 总而言之,上述除了 tryAcquire,是由RL定义的,其他都...

    AQS

    2020128

    14:29

     

    目录

    -

    - 1. AQS是一个构建锁的框架。

    - 2. AQS中的数据结构

    - 3. AQS设计模式

    - 4. 从ReentrantLock的加锁过程来理解AQS

    概述

    重要引用

    '''

    总而言之,上述除了 tryAcquire,是由RL定义的,其他都是AQS提供,AQS的获取资源,否则进入的逻辑即是acquire,它包含以下逻辑。

    • tryAcquire,需要实现,CAS申请资源
    • addWaiter,获取不到资源入队,不需要实现,注意入队也是CAS
    • acquireQueued,为队内资源分配资源,并且挂起活动的等待队列,去掉无效节点

    '''

     

    1. AQS是一个构建锁的框架。

    其原理是基于CAS + 阻塞队列 —— 尝试获取,获取不到则进入队列等待。

    AQS是区别于synchronized关键字的另一种锁实现机制,历史是,以前synchronized用mutex重量级互斥的悲观锁开销太大,因而Doug Lea大佬开发出基于CAS乐观锁的AQS。但后来synchronized进行了大量改进,例如四种级别的锁,现在它的效率通常来讲是最高的。

    常用的AQS的锁有ReentrantLock,Semaphore。

    2. AQS中的数据结构

    想知道AQS如何利用简单CAS实现线程安全,先来看看它的数据结构,即挂载线程的节点Node和队列。

    队列是类似双向链表的CLH链表,节点是普通的节点。

    static final class Node {

        static final Node EXCLUSIVE = null;

     

        static final int CANCELLED = 1;

        // waitStatus的值,表示后继结点(对应的线程)需要被唤醒

        static final int SIGNAL = -1;

        // waitStatus的值,表示该结点(对应的线程)在等待某一条件

        static final int CONDITION = -2;

        /*waitStatus的值,表示有资源可用,新head结点需要继续唤醒后继结点(共享模式下,多线程并发释放资源,而head唤醒其后继结点后,需要把多出来的资源留给后面的结点;设置新的head结点时,会继续唤醒其后继结点)*/

        static final int PROPAGATE = -3;

        // 等待状态,取值范围,-3-2-101

     

        volatile int waitStatus;

     

        volatile Node prev; // 前驱结点

         volatile Node next; // 后继结点

        volatile Thread thread; // 结点对应的线程

        Node(Thread thread, Node mode) {     // Used by addWaiter

            this.nextWaiter = mode;

            this.thread = thread;

        }

    }

    队列是这个样子的。

    3. AQS设计模式

    模板设计模式,如下图,其实也非常简单——抽象类大部分东西给你写好,流程写好,剩下一些方法的不同需要你去实现。

    对于AQS,实现AQS,需要覆盖的方法有:

    • tryAcquire(int):尝试使用CAS获取资源,资源由AQS中的标记变量标记,获取失败则入队,但是入队部分不需要我们实现。
    • tryRelease(int):释放资源。

    其实上面只是独占式的锁——与之对应还有共享式,区别就是资源是一份还是多份,多人能不能同时使用资源(比如多人读就可以)。

    4. ReentrantLock的加锁过程来理解AQS

    ReentrantLock(RL)是最常用的独占锁,来看它的加锁过程。

    • RL调用lock方法,lock调用几个内部类的lock(实现AQS),最后调用父类的acquire方法
    • AQS: acquire方法,获取资源,获取不到则进入队列
      • 源码,核心代码

    • 首先调用RL: tryAcquire方法,尝试获取资源,由RL实现

    • 由该方法实现CAS修改AQS的资源量,首先它检查state,如果可以获取,则直接尝试用CAS获取,获取不到则return false,回到acquire
    • 该方法判断过程,即使多个进程同时CAS,只有一个能获得,其他会返回fasle,这也是实现独占资源的核心
    • addWaiter进入队列

    • 这里细节不重要,是入队指针的赋值,重要的只有注释部分,CAS入队以及enq自旋
    • enq的代码非常简单,创建或者循环入队

     

    • AQS: addWaiter入队以后返回node给aquireQueued
    • AQS: aquireQueued,给队列中排前面的节点(线程)分配资源

    • 看注释其中shouldParkAfterFailedAcquire的作用如注释所示
    • aquireQueued一般情况下分配完成会return false

     

    总而言之,上述除了 tryAcquire,是由RL定义的,其他都是AQS提供,AQS的获取资源,否则进入的逻辑即是acquire,它包含以下逻辑。

    • tryAcquire,需要实现,CAS申请资源
    • addWaiter,获取不到资源入队,不需要实现,注意入队也是CAS
    • acquireQueued,为队内资源分配资源,并且挂起活动的等待队列,去掉无效节点
    展开全文
  • java锁的底层原理

    千次阅读 2019-05-09 20:46:46
    底层 升级过程、CAS操作缺点【替换线程和copy mw】 优化 代码优化:同步代码块、减少粒度、读并发 JDK自带 偏置、轻量级(CAS操作)、自适应自旋、粗化、消除 Volatile 概念:非...

    知识整理

    1. Synchronized 内置锁,JVM级别
      1. 使用
      2. 底层 锁升级过程、CAS操作的缺点【替换线程和copy mw】
      3. 优化
        1. 代码优化:同步代码块、减少锁粒度、读锁并发
        2. JDK自带 偏置锁、轻量级锁(CAS操作)、自适应自旋、锁粗化、锁消除
    2. Volatile
        1. 概念:非阻塞可见性、禁止指令重排序*
        2. 与syn区别: 无法实现原子操作、使用场景--单线程、不依赖当前值
    3. Reentrantlock 显示锁:基于AQS实现,API级别
      1. AQS原理:
        1. 数据结构:state、waitstate【signal-1、传播-3】、
        2. 独占、共享 tryAcquireShared
      2. 非公平锁
      3. 特性锁 可重入、轮询、定时、可中断
      4. 优点、使用场景
      5. 与Syn区别、Syn优点
    4. 死锁
      1. 概念:多个线程因竞争资源而互相等待的僵局;4个必要条件:资源互斥、不可剥夺、保持与请求、循环等待
      2. 死锁避免:锁顺序、锁时限、死锁检测与恢复
      3. 死锁检测与恢复:分配资源时不加条件;检测时机:进程等待、定时、利用率下降
        1. 检测算法:资源分配表、遍历锁关系图
        2. 撤销进程、设置线程随机优先级
    5. 锁模式
      1. 读锁、写锁
      2. 乐观锁:用户解决---数据版本id、时间戳;CAS;适合写操作少的场景;MVCC实现
      3. 悲观锁:数据库行锁、页锁...

     

    synchronized的4种应用方式 jvm内部实现 称为:内置锁

    synchronized关键字最主要有以下3种应用方式,都是作用在对象上

    1. 修饰类,作用范围:synchronized括号内, 作用对象:类的所有对象;synchronized(Service.class){ }
    2. 修改静态方法,作用范围:整个静态方法, 作用对象:类的所有对象;
    3. 修饰方法,被修饰的同步方法,作用范围:整个方法, 作用对象:调用这个方法的对象;
      1. 缺点:A线程执行一个长时间任务,B线程必须等待
    4. 修饰代码块,被修饰的代码块同步语句块,作用范围:大括号内的代码, 作用对象:调用这个代码块的对象;
      1. 优点:减少锁范围,耗时的代码放外面,可以异步调用

     

    notify 方法实现只唤醒一个线程,由操作

    lock.notify()方法最终通过ObjectMonitor的void notify(TRAPS)实现:

    1、如果当前_WaitSet【线程等待的集合】为空,即没有正在等待的线程,则直接返回;

    2、通过ObjectMonitor::DequeueWaiter 出队方法,获取_WaitSet列表中的第一个ObjectWaiter节点,实现也很简单【选择哪个线程取决于操作系统对多线程管理的实现】

    3、根据不同的策略,将取出来的ObjectWaiter节点,加入到Contention List,或自旋操作,CAS改变第一个节点的的指针为新增节点

     

    notifyAll方法实现

    lock.notifyAll()方法最终通过ObjectMonitor的void notifyAll(TRAPS)实现:

    通过for循环取出_WaitSet的ObjectWaiter节点,并根据不同策略,加入到_EntryList或则进行自旋操作。

    从JVM的方法实现中,可以发现:notify和notifyAll并不会释放所占有的ObjectMonitor对象,其实真正释放ObjectMonitor对象的时间点是在执行monitorexit指令,

    一旦释放ObjectMonitor对象了,Entryset中ObjectWaiter节点所保存的线程,就可以开始竞争ObjectMonitor对象进行加锁操作了,和ready线程竞争?

     

    2.锁的的实现:内存机制 copy到工作内存->   修改->   刷新主存   的过程,才会释放它得到的锁,达到线程安全。

    1. 锁住(lock)
    2. 主->从 read load 将需要的数据从主内存拷贝到自己的工作内存(read and load)
    3. 修改 use assign 根据程序流程读取或者修改相应变量值(use and assign)
    4. 从->主 store write将自己工作内存中修改了值的变量拷贝回主内存(store and write)
    5. 释放对象锁(unlock)

    线程安全:

    1. 当多个线程访问某个类,其始终能表现出正确的行为
    2. 采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,限制其他线程访问,直到锁释放

     

    Java中的锁优化 代码方式、JDK自带方式

    1.代码 锁优化

    1. 减少锁持有时间 
      1. 使用同步代码块,而非同步方法;
    2. 减小锁粒度
      1. JDK1.6中 ConcurrentHashMap采取对segment加锁而不是整个map加锁,提高并发性;
    3. 锁分离  读锁之间不互斥;读写分离
      1. 根据同步操作的性质,把锁划分为的读锁和写锁,读锁之间不互斥,提高了并发性

    2.JDK1.6 锁优化 synchronized底层

    1.引入偏向锁、轻量级锁

    1. 锁主要存在四中状态,依次是:无锁状态01、偏向锁状态01、轻量级锁状态00、重量级锁状态10,
    2. 会随着竞争的激烈而逐渐升级,锁可以升级不可降级,提高 获得锁和释放锁 效率
    3. “轻量级锁”和“偏向锁”作用:减少 获得锁和释放锁 的性能消耗

    优点

    缺点

    适用场景

    偏向锁

    记录线程iD,若该线程,则不加锁;锁状态01

    如果线程间存在锁竞争,会带来额外的锁撤销的消耗。

    适用于只有一个线程访问同步块场景。

    轻量级锁

    Mark Word复制到锁记录,CAS更新指针及标志位00

    自旋方式竞争,竞争的线程不会阻塞,提高了程序的响应速度

    如果始终得不到锁竞争的线程使用,自旋会消耗CPU。

    追求响应时间。

    同步块执行速度非常快。

    重量级锁

    CAS失败时,升级。锁状态:10

    线程阻塞,响应时间缓慢。

    追求吞吐量。

    同步块执行速度较长。

    偏向锁获取过程

    1. 访问Mark Word中偏向锁的标识是否设置成1,锁标志位是否为01,确认为可偏向状态。
    2. 如果为可偏向状态,则判断偏向线程ID是否指向当前线程,如果是,进入步骤5,否则进入步骤3。
    3. 如果线程ID并未指向当前线程,则通过CAS操作竞争锁。如果竞争成功,则将Mark Word中线程ID设置为当前线程ID,然后执行5;
      1. 如果竞争失败,执行4,偏向锁升级为轻量级锁。
    4. 如果CAS获取偏向锁失败,则表示有竞争。当到达全局安全点(safepoint)时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码。(撤销偏向锁的时候会导致stop the word)
    5. 执行同步代码

    轻量级锁

    1. 虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝【因为栈帧为线程私有,对象大家都有】
    2. 拷贝对象头中的Mark Word复制到锁记录(Lock Record)中;
    3. 拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word中的,更新为指向Lock Record的指针,并将Lock record里的owner指针指向object mark word。如果更新成功,则执行步骤4,否则执行步骤5。
    4. 如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如图所示。

     

    2.锁粗化 

    1. 如果一系列的连续操作都对同一个对象反复加锁和解锁,如循环体内,很耗性能
    2. 加锁同步的范围扩展到整个操作序列的外部:第一个append到最后一个append;不对每个append加锁

    3.锁消除 逃逸分析的数据的支持

            编译器判断到一段代码中,堆上的数据不会逃逸出当前线程,可以认为是线程安全的,不必加锁

    4.自旋与自适应自旋:想要获取锁的线程做几个空循环 10 CAS实现

    .为什么引入:

    轻量级锁失败后,线程会在操作系统层面挂起

    操作系统实现线程之间的切换时,需要从用户态转换到核心态,状态转换耗时

    .解决方法:

    当线程在获取轻量级锁时CAS操作失败时,通过自旋让线程等待,避免线程切换的开销

    假设不久当前的线程可以获得锁,虚拟机会让当前想要获取锁的线程做几个空循环,可能是50个循环或100循环

    结果:

    如果得到锁,就顺利进入临界区;如果不能,就将线程在操作系统层面挂起,升级为重量级锁

    自旋锁的优化:自适应自旋

    自旋是需要消耗CPU的,如果一直获取不到锁,线程一直自旋,浪费CPU资源

    线程如果自旋成功了,下次自旋的次数会更多,自旋失败了,自旋的次数就会减少。

     

    CAS底层实现原理    用于更新数据

    CAS:Compare and Swap, 翻译成比较并交换 

    CAS需要在:操作值的时候,检查值有没有发生变化,如果没有发生变化则更新,最终都会返回内存地址,且是原子操作

    需要3个操作数:内存地址V,旧预期值A、新值B

    当且仅当V符合预期值A时(即V存储的值无变化),用B更新A,否则不执行更新,最终都返回内存地址V

    适用场景:

    1. 适合资源竞争较少的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;
    2. CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,可以获得更高的性能
    3. synchronized在jdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS
    4. 原子类中都使用到了CAS

     

    volatile详解

    非可见性:

    编译器为了加快程序运行的速度,对一些变量的写操作会先在(工作内存)寄存器或者是CPU缓存上进行,最后才写入内存,这个过程中,变量的新值对其他线程是不可见的

    1.实现对所有线程的可见性 SMP:对称多处理器架构通过总线BUS进行 Cache一致性流量 通信

    1. volatile保证新值立即同步到主存,
    2. 线程对变量读取的时候,要从主内存中读,而不是缓存
    3. 变量的赋值一旦变化就会通知到其他线程,如果其他线程的工作内存中存在这个同一个变量拷贝副本,那么其他线程会放弃这个副本中变量的值,重新去主内存中获取

    适用场景:

    确保只有单一的线程修改变量的值 或 运算结果不依赖当前变量值(i++时,运算结果依赖当前变量,且是多个线程改变)

    变量不需要与其他的状态变量共同参与不变约束

    2.实现 禁止指令重排序优化 应用:双边检查单例

    1. 为了减少CPU空闲时间,java不能保证程序执行的顺序与代码中一致,
    2. volatile修饰的变量相当于生成内存屏障,重排序时不能把后面的指令排到屏障之前;指令屏障
    3. 作用:为了保证happen-before原则:写、锁lock、传递性、线程启动、中断、终结、对象创建的先后关系
    1. 定义了线程、锁、volatile变量、对象创建的先后关系
    2. 若满足,则保证一个操作执行的结果需要对另一个操作可见
    3. 判断数据是否存在竞争、线程是否安全的依据

    内存屏障实现方式;volatile的内存语义

    编译器在生成字节码时,会在指令序列中,插入内存屏障来禁止特定类型的处理器重排序;

    写前后、读后后;

    写写【写上下】写读、【读后面】读读、读写

     

    1.volatile写操作的前面插入一个StoreStore屏障:禁止上面的写

    2.volatile写操作的后面插入一个SotreLoad屏障:禁止下面的读

    3.volatile读操作的后面插入一个LoadLoad屏障:禁止下面的读

    4.volatile读操作的后面插入一个LoadStore屏障:禁止下面的写

     

     

    3.无法实现 i++ 原子操作

    A读取 i 后,B也读取 i ,此时A进行 +1,B的 i 就变了

    原子类如何解决:CAS,B在进行+1时,检查此时的 i 跟主存的 i 是否一致,一致才+1;原子类

    synchronized和volatile区别 锁的目标:关注互斥性和可见性

    1.锁提供了两种主要特性:互斥性(mutual exclusion) 和可见性(visibility)。

      互斥即一次只允许一个线程持有某个锁,使用该共享数据。

      可见性确保新值立即同步到主存,每次使用前立即从主内存刷新

    2.概念

    synchronized同步阻塞:释放锁之前会将对变量的修改刷新到主存当中;

    volatile关键字非阻塞:确保新值立即同步到主存,其他线程每次使用前立即从主内存刷新;

    3.区别

    1)volatile非阻塞,synchronized只有当前线程可以访问修饰的变量,其他线程阻塞

    2)volatile仅能修饰变量,synchronized则可以使用在变量,方法.

    3)volatile仅能实现变量的修改可见性,而synchronized则可以保证变量的修改可见性和原子性(操作不可分割)

     

    可重入锁 Re entrantlock

    显示锁:基于JDK API、AQS、乐观锁实现、需要显式的加锁以及释放锁

    1. 无阻塞的同步机制(非公平锁实现)
    2. 可实现轮询锁、定时锁、可中断锁特性;
    3. 提供了一个Condition(条件)类,对锁进行更精确的控制
    4. 默认使用非公平锁,可插队跳过对线程队列的处理(因此被称为可重入)
      1. ReentrantLock的内部类Sync继承了AQS,分为公平锁FairSync和非公平锁NonfairSync。
      2. 公平锁:线程获取锁的顺序和调用lock的顺序一样,FIFO;唤醒锁的时间CPU浪费;是否是AQS队列中的头结点
      3. 非公平锁:线程获取锁的顺序和调用lock的顺序无关,先执行lock方法的锁不一定先获得锁
    5. 加锁和解锁都需要显式写出,实现了Lock接口,注意一定要在适当时候unlock
    6. 总结:公平锁与非公平锁对比
    • FairSync:lock()少了插队部分(即少了CAS尝试将state从0设为1,进而获得锁的过程)
    • FairSync:tryAcquire(int acquires)多了需要判断当前线程是否在等待队列首部的逻辑(实际上就是少了再次插队的过程,但是CAS获取还是有的)。

    公平锁的核心

    1. 获取一次锁数量,state值
      1. 如果锁数量为0,如果当前线程是等待队列中的头节点,基于CAS尝试将state(锁数量)从0设置为1一次,如果设置成功,设置当前线程为独占锁的线程;
      2. 如果锁数量不为0或者当前线程不是等待队列中的头节点或者上边的尝试又失败了,查看当前线程是不是已经是独占锁的线程了,如果是,则将当前的锁数量+1;如果不是,则将该线程封装在一个Node内,并加入到等待队列中去。等待被其前一个线程节点唤醒。

    非公平锁 两者都是非公平锁

    1. 非公平锁,可以直接插队获取锁,跳过了对队列的处理,速度会更快
      1. 公平锁为了保证线程规规矩矩地排队,需要增加阻塞和唤醒的时间开销;
    2. AQS底层原理:在lock获取锁时首先判断当前锁是否可以用(AQS的state状态值是否为0),如果是 直接“插队”获取锁,否则进入排队队列,并阻塞当前线程; 充分利用了唤醒线程的时间【Singel标志唤醒,需要前驱节点唤醒】

     

    非公平锁加锁的简单步骤

    基于CAS尝试将state(锁数量)从0设置为1 ---第一次插队

    1. 如果设置成功,设置当前线程为独占锁的线程;
    2. 如果设置失败,还会再获取一次锁数量,---第二次插队
      1. 如果锁数量为0,再基于CAS尝试将state(锁数量)从0设置为1一次,如果设置成功,设置当前线程为独占锁的线程;
      2. 如果锁数量不为0或者上边的尝试又失败了,查看当前线程是不是已经是独占锁的线程了,如果是,则将当前的锁数量+1;如果不是,则将该线程封装在一个Node内,并加入到等待队列中去。等待被其前一个线程节点唤醒
    3. 入队后,无限循环tryAcquire(1)方法 ---第三次插队

     

    非公平锁源码

    1.基于CAS将state(锁数量)从0设置为1,如果设置成功,设置当前线程为独占锁的线程;-->第一次插队

    若失败,调用acquire(1)->tryAcquire(1),acquireQueued(addWaiter(Node.EXCLUSIVE)

    public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt();//请求锁成功,中断自己 }

    tryAcquire(arg)会调用nonfairTryAcquire(1)调用,作用:第二次插队请求锁

    final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread();//获取当前线程 int c = getState();//获取锁数量 if (c == 0) {//如果锁数量为0,证明该独占锁已被释放,当下没有线程在使用 if (compareAndSetState(0, acquires)) {//继续通过CAS将state由0变为1,注意这里传入的acquires为1 setExclusiveOwnerThread(current);//将当前线程设置为独占锁的线程 return true; } } else if (current == getExclusiveOwnerThread()) {//查看当前线程是不是就是独占锁的线程 int nextc = c + acquires;//如果是,锁状态的数量为当前的锁数量+1 if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc);//设置当前的锁数量 return true; } return false; }

    若请求锁失败,将当前线程链入队尾并挂起,之后等待被唤醒 【快速、正常】

    1. 首先会使用addWaiter(Node.EXCLUSIVE)将当前线程封装进Node节点node,然后将该节点加入等待队列
    2. 先快速入队【存在尾节点,将使用CAS尝试将尾节点设置为node】
    3. 如果快速入队不成功【尾节点为空】,使用正常入队方法enq,无限循环=第一次阻塞,直到Node节点入队为止【创建一个dummy节点,并将该节点通过CAS设置到头节点,若头结点不为null,cas继续快速入队】

    入队成功后返回node节点,继续第三次插队

    无限循环调用:acquireQueued(final Node node, int arg)获取node的前驱节点p

    p==head&&tryAcquire(1) 是唯一跳出循环的方法:p成为头结点并且获取锁成功:如果p是头节点,就继续使用tryAcquire(1)方法插队,若成功,不用中断,第三次插队成功

    1. 如果p不是头节点,或者tryAcquire(1)请求不成功,执行shouldParkAfterFailedAcquire(Node pred, Node node)来检测当前节点是不是可以安全的被挂起:判断p的等待状态waitStatus
      1. SIGNAL(即可以唤醒下一个节点的线程),则node节点的线程可以安全挂起,返回true
      2. CANCELLED,则p的线程被取消了,我们会将p之前的连续几个被取消的前驱节点从队列中剔除
      3. 等待状态是除了上述两种的其他状态,CAS尝试将前驱节点的等待状态设为SIGNAL【p与node竞争】

    挂起后后 跳出循环,需要中断自身

    LockSupport.park(this);//挂起当前的线程,后等待前去节点unpark唤醒该线程;方法为public

     

    DelayQueue中的使用示例:

    1. take()和offer()都是lock了重入锁,按照synchronized的公平锁,两个方法是互斥
    2. take()方法需要等待1个小时才能返回,offer()需要马上提交一个10秒后运行的任务,此时offer()可以插队获取锁
    3. 原理:A执行时,B lock()锁,并休眠;当锁被A释放处于可用状态时,B线程却还处于被唤醒的过程中,此时C线程请求锁,可以优先C得到锁

     

    Reentrantlock优点

    1. 显示锁可中断,防止死锁,内置锁不可中断,会产生死锁
    2. 实现其他特性的锁
    3. 对锁更精细的控制

     

    synchronized优点

    1. 显示锁易忘记 finally 块释放锁,对程序有害
    2. 显示锁只能用在代码块,强制更细粒度的加锁;syn可以用在方法上
    3. synchronized 管理锁定和释放时,能标识死锁或者其他异常行为的来源,利于调试
    4. Synchronized引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多

    使用场景

    1. Condition类对锁进行更精确的控制,指定唤醒、分组唤醒
    2. 防止死锁
    3. 轮询锁:用tryLock(long timeout, TimeUnit unit)和tryLock() 这两个方法实现,即没有获取到锁,可以使用while循环 隔一段时间再次获取,直到获取到为止
    4. 定时锁:指的是在指定时间内没有获取到锁,就取消阻塞并返回获取锁失败;tryLock(long timeout, TimeUnit unit)
    5. 可中断锁:lockInterruptibly,防止死锁

     

    区别

    synchronied是JVM级别的,而ReentrantLock是api级别的

    JVM会对synchronied做出相应的优化

    锁消除:JVM判断堆上的数据不会逃逸出当前线程,不加锁;

    自旋锁

    自适应锁

     

    提供的lock()方法:

    重入锁实现 直到state为0,其他锁才可以用

    1. 如果该锁没有被另一个线程持有,则获取该锁并立即返回,将锁计数设置为 1;对应AQS中的state
    2. 如果当前线程已经持有该锁,将锁计数加 1,并立即返回方法---重入锁
    3. 如果该锁被另一个线程持有,则禁用当前线程,在获得锁之前,一直休眠,此时锁保持计数设置为 1

    排他锁实现:Lock类有读锁和写锁,读读共享,写写互斥,读写互斥:每次获取锁时都是首先判断state是否为0,并且只有1个线程能获取到锁

    tryLock和lock和lockInterruptibly的区别

    1. tryLock能获得锁就返回true,不能就立即返回false,可以增加时间限制,如果超过该时间段还没获得锁,返回false;tryLock(long timeout,TimeUnit unit),
    2. lock能获得锁就返回true,不能的话一直等待获得锁
    3. lockInterruptibly,中断会抛出异常

     

    锁的Condition类

    Lock类可以创建Condition对象,Condition对象用来是线程等待和唤醒线程;Condition condition=lock.newCondition();

    对锁进行更精确的控制

    1. Condition中的await()方法相当于Object的wait()方法
    2. Condition中的signal()方法相当于Object的notify()方法
    3. Condition中的signalAll()相当于Object的notifyAll()方法
    4. ReentrantLock类可以唤醒指定条件的线程,而object的唤醒是随机的

     

    Condition函数列表

    1. 造成当前线程在接到信号或被中断之前一直处于等待状态 void await()
    2. 唤醒一个等待线程 void signal()
    3. 唤醒所有等待线程 void signalAll()
    4. 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态 boolean await(long time, TimeUnit unit)
    5. 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态 long awaitNanos(long nanosTimeout)
    6. 造成当前线程在接到信号之前一直处于等待状态 void awaitUninterruptibly()
    7. 造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态 boolean awaitUntil(Date deadline)

     

     

    共享锁实现 并发读 ReentrantReadWriteLock、计数器

    共享锁的AQS实现

    实现tryAcquireShared方法【检查下一个节点是共享节点】,获取共享锁,锁在所有调用await方法的线程间共享

    底层:

    1. 在AQS队列中,将线程包装为Node.SHARED节点,即标志为共享锁
    2. 当头节点获得共享锁后,唤醒下一个共享类型结点的操作
      1. 头节点node1调用unparkSuccessor()方法唤醒了Node2,并且调用tryAcquireShared方法检查下一个节点是共享节点
      2. 如果是,更改头结点,重复以上步骤,以实现节点自身获取共享锁成功后,唤醒下一个共享类型结点的操作

    应用:

    1.ReentrantReadWriteLock

    2.CountDownLatch为java.util.concurrent包下的计数器工具类

    可被多线程并发的实现减1操作,并在计数器为0后,调用await方法的线程被唤醒,从而实现多线程间的协作

    new CountDownLatch(3).countDown();

    用来实现等所有共享锁线程都唤醒后一起协作

     

    死锁

    多个线程因竞争资源而造成僵局(互相等待),无法向前推进

    产生的原因

    1) 系统资源的竞争

    系统不可剥夺资源,数量不足以满足多个进程运行,使得进程在运行过程中,因竞争资源而陷入僵局

    2) 进程推进顺序非法

    请求和释放资源的顺序不当,也同样会导致死锁。如,互相申请各占有的资源。

    信号量使用不当也会造成死锁。进程间彼此相互等待消息,结果也会使得这 些进程间无法继续向前推进。

    3) 死锁产生的4个必要条件,只要其中任一条件不成立,死锁就不会发生

    1. 资源互斥条件:资源互斥,即某资源仅为一个进程占有
    2. 资源不可剥夺条件:进程所获得的资源在未使用完毕之前,只能是主动释放,不能被其他进程强行夺走
    3. 保持和请求条件:进程已经保持了一个资源,又提出了新的资源请求,而该资源已被其他进程占有
    4. 循环等待条件:进程资源循环等待

     

    如何避免死锁

    1. 加锁顺序(线程按照一定的顺序加锁)
      1. 按照顺序加锁是一种死锁预防机制,需要事先知道所有会用到的锁
    2. 加锁时限(超时则放弃)
      1. 获取锁时加上时限,超过时限则放弃请求,并释放锁,等待一段随机的时间再重试
    3. 死锁检测与恢复
      1. 操作系统中:系统为进程分配资源,不采取任何限制性措施,提供检测和恢复的手段

    死锁检测:当一个线程请求锁失败时,遍历锁的关系图检测死锁

    死锁恢复

    1. 撤消进程,剥夺资源
    2. 线程设置优先级,让一个(或几个)线程回退,剩下的线程就像没发生死锁一样继续保持着它们需要的锁
    3. 死锁发生的时候设置随机的优先级;如果赋予这些线程的优先级是固定不变的,同一批线程总是会拥有更高的优先级。

     

    锁模式包括: 

    1. 共享锁:(读取)用户可以并发读取数据,但不能获取写锁,直到释放所有读锁。
    2. 排他锁(写锁):加上写锁后,其他线程无法加任何锁;写锁可以读和写
    3. 更新锁: 防止死锁而设立,转换读锁为写锁之前的准备,仅一个线程可获得更新锁

    乐观锁:认为数据一般情况下不会造成冲突,在数据提交更新时,才进行数据的冲突检测;

    如果冲突,返回信息让用户决定如何去做。实现方式:记录数据版本。

    悲观锁:操作数据时上锁保护,限制其他线程访问,直到该锁释放。关系型数据库锁机制,行锁、页锁、表锁,都是在做操作之前先上锁。

    锁的粒度: 都是悲观锁

    1. 行锁: 粒度最小,并发性最高
    2. 页锁:锁定一页。25个行锁可升级为一个页锁。
    3. 表锁:粒度大,并发性低
    4. 数据库锁:控制整个数据库操作

     

    Happen-Before原则 八大原则:

    1. 定义了线程、锁、volatile变量、对象创建的先后关系
    2. 若满足,则保证一个操作执行的结果需要对另一个操作可见
    3. 判断数据是否存在竞争、线程是否安全的依据
    • 单线程:在同一个线程中,书写在前面的操作happen-before后面的操作。
    • 锁:解锁先于锁定;同一个锁的unlock操作happen-before此锁的lock操作。
    • volatile:先写;对一个volatile变量的写操作happen-before对此变量的任意操作(当然也包括写操作了)。
    • 传递性原则:如果A操作 happen-before B操作,B操作happen-before C操作,那么A操作happen-before C操作。
    • 线程启动:start方法优先;同一个线程的start方法happen-before此线程的其它方法。
    • 线程中断:对线程interrupt方法的调用happen-before被中断线程的检测到中断发送的代码。
    • 线程终结:线程中的所有操作都happen-before线程的终止检测。
    • 对象创建:先初始化,后finalize;一个对象的初始化完成先于他的finalize方法调用。

     

    展开全文
  • Java多线程:锁的底层实现

    千次阅读 2018-07-31 11:39:11
    最近准备招聘,在网上搜集了很多关于JVM获取的锁流程文章,基本上所有关于偏向获取部分都是错,所以专门花时间自己看了看代码,整理出来供大家参考。 一、 基本概念 CAS(compare and swap) 参考...
  • [TOC] 视频地址: 马士兵老师公开课视频地址 用户态、内核态 以前操作系统,os(操作系统)和app(用户应用程序)是在同一...Hostpot(OracleJVM实现) 1:1。 JVM每个线程对应OS中一个线程。 线程、线程调度都交给
  • java锁的剖析 无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态 内存中的java对象(HotSpot虚拟机) 在32位系统下,存放Class指针的空间大小是4字节,MarkWord是4字节,对象头为8字节。 在64位系统下...
  • 关于java lock的底层实现原理,讲的有点深,转载学习! Lock完全用Java写成,在java这个层面是无关JVM实现的。 在java.util.concurrent.locks包中有很多Lock的实现类,常用的有ReentrantLock、ReadWriteLock(实现类...
  • 谈到多线程就不得不谈到Synchronized,很多同学只会使用,缺不是很明白整个Synchronized的底层实现原理,这也是面试经常被问到的环节,比如: synchronized的底层实现原理 synchronized与JVM的实现 synchronized...
  • CAS(Compare And Swap 比较并且替换)是乐观锁的一种实现方式,是一种轻量级锁,JUC 中很多工具类的实现就是基于 CAS 的,也可以理解为自旋锁 JUC是指import java.util.concurrent下面的包, 比如:import java....
  • lock锁的底层实现

    千次阅读 2018-04-15 18:02:42
    这当然取决于它们的底层实现,所以今天我们就先来看一下lock锁的底层原理吧,因为lock锁的底层实现这块内容比较多,所以sychronized我会再写一篇博文和大家分享~ Lock锁通过Java编写,与JVM实现无关。 在J.U.C....
  • 谈到多线程就不得不谈到Synchronized,很多同学只会使用,缺不是很明白整个Synchronized的底层实现原理,这也是面试经常被问到的环节,比如: synchronized的底层实现原理 synchronized与JVM的实现 ...
  • 锁的底层实现: 由Java 虚拟机中的同步(Synchronization)基于进入和退出管程(Monitor)对象实现。同步方法 并不是由 monitor enter 和 monitor exit 指令来实现同步的,而是由方法调用指令读取运行时常量池中方法的...
  • 接下里将会解释同步代码块、同步方法、全局锁的底层实现。 同步代码块底层实现: 首先看一个简单的同步代码块: /////同步代码块底层实现 public class DiCeng { public static void main(String[] args) ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,838
精华内容 735
关键字:

java锁的底层实现

java 订阅