精华内容
下载资源
问答
  • 自旋可以使线程在没有取得的时候,不被挂起,而转去执行一个空循环,(即所谓的自旋,就是自己执行空循环),若在若干个空循环后,线程如果可以获得,则继续执行。若线程依然不能获得,才会被挂起。 使用...
     
    1、自旋锁
    自旋锁可以使线程在没有取得锁的时候,不被挂起,而转去执行一个空循环,(即所谓的自旋,就是自己执行空循环),若在若干个空循环后,线程如果可以获得锁,则继续执行。若线程依然不能获得锁,才会被挂起。
    使用自旋锁后,线程被挂起的几率相对减少,线程执行的连贯性相对加强。因此,对于那些锁竞争不是很激烈,锁占用时间很短的并发线程,具有一定的积极意义,但对于锁竞争激烈,单线程锁占用很长时间的并发程序,自旋锁在自旋等待后,往往毅然无法获得对应的锁,不仅仅白白浪费了CPU时间,最终还是免不了被挂起的操作 ,反而浪费了系统的资源。
    在JDK1.6中,Java虚拟机提供-XX:+UseSpinning参数来开启自旋锁,使用-XX:PreBlockSpin参数来设置自旋锁等待的次数。
    在JDK1.7开始,自旋锁的参数被取消,虚拟机不再支持由用户配置自旋锁,自旋锁总是会执行,自旋锁次数也由虚拟机自动调整。

    可能引起的问题:
    1.过多占据CPU时间:如果锁的当前持有者长时间不释放该锁,那么等待者将长时间的占据cpu时间片,导致CPU资源的浪费,因此可以设定一个时间,当锁持有者超过这个时间不释放锁时,等待者会放弃CPU时间片阻塞;
    2.死锁问题:试想一下,有一个线程连续两次试图获得自旋锁(比如在递归程序中),第一次这个线程获得了该锁,当第二次试图加锁的时候,检测到锁已被占用(其实是被自己占用),那么这时,线程会一直等待自己释放该锁,而不能继续执行,这样就引起了死锁。因此递归程序使用自旋锁应该遵循以下原则:递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁。

    2、阻塞锁
    让线程进入阻塞状态进行等待,当获得相应的信号(唤醒,时间) 时,才可以进入线程的准备就绪状态,准备就绪状态的所有线程,通过竞争,进入运行状态。。
    JAVA中,能够进入\退出、阻塞状态或包含阻塞锁的方法有 ,synchronized 关键字(其中的重量锁),ReentrantLock,Object.wait()\notify()

    3、可重入锁
    可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。
    在JAVA环境下 ReentrantLock 和synchronized 都是 可重入锁
    下面是使用实例
     
    public class Test implements Runnable{
    
    	public synchronized void get(){
    		System.out.println(Thread.currentThread().getId());
    		set();
    	}
    
    	public synchronized void set(){
    		System.out.println(Thread.currentThread().getId());
    	}
    
    	@Override
    	public void run() {
    		get();
    	}
    	public static void main(String[] args) {
    		Test ss=new Test();
    		new Thread(ss).start();
    		new Thread(ss).start();
    		new Thread(ss).start();
    	}
    }
    
    public class Test implements Runnable {
    	ReentrantLock lock = new ReentrantLock();
    
    	public void get() {
    		lock.lock();
    		System.out.println(Thread.currentThread().getId());
    		set();
    		lock.unlock();
    	}
    
    	public void set() {
    		lock.lock();
    		System.out.println(Thread.currentThread().getId());
    		lock.unlock();
    	}
    
    	@Override
    	public void run() {
    		get();
    	}
    
    	public static void main(String[] args) {
    		Test ss = new Test();
    		new Thread(ss).start();
    		new Thread(ss).start();
    		new Thread(ss).start();
    	}
    }

    两个例子最后的结果都是正确的,即 同一个线程id被连续输出两次。
    结果如下:
    Threadid: 8
    Threadid: 8
    Threadid: 10
    Threadid: 10
    Threadid: 9
    Threadid: 9
    可重入锁最大的作用是避免死锁
    我们以自旋锁作为例子,
     
    public class SpinLock {
    	private AtomicReference<Thread> owner =new AtomicReference<>();
    	public void lock(){
    		Thread current = Thread.currentThread();
    		while(!owner.compareAndSet(null, current)){
    		}
    	}
    	public void unlock (){
    		Thread current = Thread.currentThread();
    		owner.compareAndSet(current, null);
    	}
    }

    对于自旋锁来说,
    1、若有同一线程两调用lock() ,会导致第二次调用lock位置进行自旋,产生了死锁
    说明这个锁并不是可重入的。(在lock函数内,应验证线程是否为已经获得锁的线程)
    2、若1问题已经解决,当unlock()第一次调用时,就已经将锁释放了。实际上不应释放锁。
    (采用计数次进行统计)
    修改之后,如下:
    public class SpinLock1 {
    	private AtomicReference<Thread> owner =new AtomicReference<>();
    	private int count =0;
    	public void lock(){
    		Thread current = Thread.currentThread();
    		if(current==owner.get()) {
    			count++;
    			return ;
    		}
    		while(!owner.compareAndSet(null, current)){
    		}
    	}
    	public void unlock (){
    		Thread current = Thread.currentThread();
    		if(current==owner.get()){
    			if(count!=0){
    				count--;
    			}else{
    				owner.compareAndSet(current, null);
    			}
    		}
    	}
    }
    该自旋锁即为可重入锁。

    4 悲观锁和乐观锁
    悲观锁(Pessimistic Lock), 顾名思义就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。独占锁是悲观锁的一种实现

    乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。使用CAS来保证,保证这个操作的原子性

    两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。

    参考:http://www.cnblogs.com/softidea/p/5309312.html
    http://blog.csdn.net/hongchangfirst/article/details/26004335

    5 轮询锁和定时锁
    由tryLock实现,与无条件获取锁模式相比,它们具有更完善的错误恢复机制。可避免死锁的发生:
    boolean tryLock():仅在调用时锁为空闲状态才获取该锁。如果锁可用,则获取锁,并立即返回值 true。如果锁不可用,则此方法将立即返回值 false。
     
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException:
      如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。
     
      如果锁可用,则此方法将立即返回值 true。如果锁不可用,出于线程调度目的,将禁用当前线程,并且在发生以下三种情况之一前,该线程将一直处于休眠状态:
     
      锁由当前线程获得;或者
      其他某个线程中断当前线程,并且支持对锁获取的中断;或者
      已超过指定的等待时间
      如果获得了锁,则返回值 true。
     
      如果当前线程:
     
      在进入此方法时已经设置了该线程的中断状态;或者
      在获取锁时被中断,并且支持对锁获取的中断,
      则将抛出 InterruptedException,并会清除当前线程的已中断状态。
      如果超过了指定的等待时间,则将返回值 false。如果 time 小于等于 0,该方法将完全不等待。

    6 显示锁和内置锁
    显示锁用Lock来定义、内置锁用syschronized。
    内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
    内置锁是互斥锁。

    7 读-写锁
    Lock接口以及对象,使用它,很优雅的控制了竞争资源的安全访问,但是这种锁不区分读写,称这种锁为普通锁。为了提高性能,Java提供了读写锁,在读的地方使用读锁,在写的地方使用写锁,灵活控制,如果没有写锁的情况下,读是无阻塞的,在一定程度上提高了程序的执行效率。
    Java中读写锁有个接口java.util.concurrent.locks.ReadWriteLock,也有具体的实现ReentrantReadWriteLock,详细的API可以查看JavaAPI文档。
    ReentrantReadWriteLock 和 ReentrantLock 不是继承关系,但都是基于 AbstractQueuedSynchronizer 来实现。
    lock方法 是基于CAS 来实现的
    ReadWriteLock中暴露了两个Lock对象:

    在读写锁的加锁策略中,允许多个读操作同时进行,但每次只允许一个写操作。读写锁是一种性能优化的策略。

    RentrantReadWriteLock在构造时也可以选择是一个非公平的锁(默认)还是公平的锁。

    8 对象锁和类锁
    java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。
    类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的.
    synchronized只是一个内置锁的加锁机制,当某个方法加上synchronized关键字后,就表明要获得该内置锁才能执行,并不能阻止其他线程访问不需要获得该内置锁的方法。

    调用对象wait()方法时,会释放持有的对象锁,以便于调用notify方法使用。notify()调用之后,会等到notify所在的线程执行完之后再释放锁


    9:锁粗化(Lock Coarsening):
    锁粗化的概念应该比较好理解,就是将多次连接在一起的加锁、解锁操作合并为一次,将多个连续的锁扩展成一个范围更大的锁。举个例子:
     
    1 package com.paddx.test.string;
     2 
     3 public class StringBufferTest {
     4     StringBuffer stringBuffer = new StringBuffer();
     5 
     6     public void append(){
     7         stringBuffer.append("a");
     8         stringBuffer.append("b");
     9         stringBuffer.append("c");
    10     }
    11 }

      这里每次调用stringBuffer.append方法都需要加锁和解锁,如果虚拟机检测到有一系列连串的对同一个对象加锁和解锁操作,就会将其合并成一次范围更大的加锁和解锁操作,即在第一次append方法时进行加锁,最后一次append方法结束后进行解锁。

    10 互斥锁
    互斥锁, 指的是一次最多只能有一个线程持有的锁。如Java的Lock


    15 无锁状态-》偏向锁-》轻量级锁-》重量级锁。锁膨胀
     锁的状态
    总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)。JDK 1.6中默认是开启偏向锁和轻量级锁的,
    锁膨胀:从轻量锁膨胀到重量级锁是在轻量级锁解锁过程发生的。
    重量级锁:Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”。
    轻量级锁:“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的。但是,首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。在解释轻量级锁的执行过程之前,先明白一点,轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。
    偏向锁: 引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。上面说过,轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能。
    无锁状态:在代码进入同步块的时候,如果同步对象锁状态为无锁状态。
    重量级锁、轻量级锁和偏向锁之间转换:

    11 锁消除(Lock Elimination):锁消除即删除不必要的加锁操作。根据代码逃逸技术,如果判断到一段代码中,堆上的数据不会逃逸出当前线程,那么可以认为这段代码是线程安全的,不必要加锁。看下面这段程序:

     
     package com.paddx.test.concurrent;
     
      public class SynchronizedTest02 {
      
         public static void main(String[] args) {
             SynchronizedTest02 test02 = new SynchronizedTest02();
              //启动预热
              for (int i = 0; i < 10000; i++) {
                 i++;
             }
            long start = System.currentTimeMillis();
             for (int i = 0; i < 100000000; i++) {
                 test02.append("abc", "def");
             }
             System.out.println("Time=" + (System.currentTimeMillis() - start));
         }
     
         public void append(String str1, String str2) {
             StringBuffer sb = new StringBuffer();
             sb.append(str1).append(str2);
         }
     }

    虽然StringBuffer的append是一个同步方法,但是这段程序中的StringBuffer属于一个局部变量,并且不会从该方法中逃逸出去,所以其实这过程是线程安全的,可以将锁消除。下面是我本地执行的结果
     
    12、信号量
    线程同步工具:Semaphore

    http://ifeve.com/java_lock_see/
    http://www.cnblogs.com/paddix/p/5405678.html
    http://www.cnblogs.com/softidea/p/5530761.html
     
    展开全文
  • HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Object Header)、实例数据(Instance Data)和对齐填充(Padding)。 对象头(Object Header)  JVM的对象头包括二/三部分信息:1、Mark ...

    HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Object Header)、实例数据(Instance Data)和对齐填充(Padding)。

    对象头(Object Header)

     JVM的对象头包括二/三部分信息:1、Mark Word;2、 类型指针;3、数组长度(只有数组对象才有)

     1、Mark Word

    用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等,这部分数据的长度在32位和64位的虚拟机(暂 不考虑开启压缩指针的场景)中分别为32个和64个Bits。

    Mark Word在不同的锁状态下存储的内容不同,在32位JVM中是这么存的:

    锁状态 25bit 4bit 1bit 2bit
    23bit 2bit 是否偏向锁 锁标志位
    无锁 对象的HashCode 分代年龄 0 01
    偏向锁 线程ID Epoch偏向时间戳 分代年龄 1 01
    轻量级锁 指向栈中锁记录的指针 00
    重量级锁 指向重量级锁的指针 10
    GC标记 11

    其中无锁和偏向锁的锁标志位都是01,只是在前面的1bit区分了这是无锁状态还是偏向锁状态。

    2、 类型指针 

    即是对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。 并不是所有的虚拟机实现都必须在对象数据上保留类型指针,即查找对象的元数据信息并不一定要经过对象本身。

    3、数组长度 

    如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中无法确定数组的大小。 

    偏向锁

     引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要多次CAS获取锁的执行过程,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子操作(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。偏向锁则是在只有一个线程或者多个线程不同步执行同步块时进一步提高性能。

    轻量级锁

    轻量级锁是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景,如果线程阻塞需要线程之间完成线程间的通信,耗费性能(用户态和内核态状态切换),所以直接让线程进入自旋等待锁释放。

    1、偏向锁获取过程:

      (1)访问Mark Word中偏向锁的标识位(1bit)是否设置成1,锁标志位(2bit)是否为01——确认为可偏向状态。

      (2)如果为可偏向状态,则偏向锁线程ID是否指向当前线程,如果是,进入步骤(4),否则进入步骤(3)。

      (3)如果偏向锁线程ID并未指向当前线程,因为偏向锁不会主动释放,所以当前线程可以看到对象时偏向状态以及拥有该对象锁的线程,这时表明在这个对象上已经存在竞争了,检查原来持有该对象锁的线程是否依然存活,如果挂了,则可以将对象变为无锁状态,然后重新CAS操作偏向新的线程,然后执行(4);如果原来的线程依然存活,由偏向锁升级为轻量级锁(在下面进行分析)。

      (4)执行同步代码。

    2、偏向锁升级为轻量级锁过程

    偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁。偏向锁的撤销,需要当到达全局安全点(safepoint)时获得偏向锁的线程被挂起,需要查看Java对象头中记录的偏向锁的线程是否存活,如果没有存活,那么修改对象头偏向锁标志位为0,其它线程可以竞争将其设置为偏向锁;如果存活,拷贝对象头中的Mark Word到该线程的栈帧中的锁记录里让lock record的指针指向锁对象头中的Mark Word,再让Mark Word指向指向lock record,唤醒线程继续执行原来线程的同步代码,则当前线程通过CAS操作竞争锁,竞争失败执行自旋操作继续竞争锁。

      ps: 全局安全点safepoint 参考 理解JVM的safepoint

    3、轻量级锁获取过程 

    线程的栈帧中有一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word。 

    线程会拷贝对象头中的Mark Word到锁记录(Lock Record)中,然后使用CAS操作尝试将锁对象的Mark Word指针指向Lock Record,并将线程栈帧中的Lock Record里的owner指针指向Object的 Mark Word。如果更新成功,则表示获取了轻量级锁。

    4、轻量级锁膨胀重量级锁过程

    a线程获得锁,会在a线程的栈帧里创建lock record(锁记录),让lock record的指针指向锁对象的对象头中的mark word.再让mark word 指向lock record.这就是获取了锁。如果b线程在锁竞争时,发现锁已经被a线程占用,则b线程不进入内核态,让b线程自旋,执行空循环,等待a线程释放锁。如果发现a线程没有释放锁,这时候c线程也来竞争锁,那么这个时候轻量级锁就会膨胀为重量级锁。

     

    展开全文
  • 自旋可以使线程在没有取得的时候,不被挂起,而转去执行一个空循环,(即所谓的自旋,就是自己执行空循环),若在若干个空循环后,线程如果可以获得,则继续执行。若线程依然不能获得,才会被挂起。 使用...
    参考:http://blog.csdn.net/a314773862/article/details/54095819


    自旋锁

    自旋锁可以使线程在没有取得锁的时候,不被挂起,而转去执行一个空循环,(即所谓的自旋,就是自己执行空循环),若在若干个空循环后,线程如果可以获得锁,则继续执行。若线程依然不能获得锁,才会被挂起。

    使用自旋锁后,线程被挂起的几率相对减少,线程执行的连贯性相对加强。因此,对于那些锁竞争不是很激烈,锁占用时间很短的并发线程,具有一定的积极意义,但对于锁竞争激烈,单线程锁占用很长时间的并发程序,自旋锁在自旋等待后,往往毅然无法获得对应的锁,不仅仅白白浪费了CPU时间,最终还是免不了被挂起的操作 ,反而浪费了系统的资源。

    可能引起的问题:
    1.过多占据CPU时间:如果锁的当前持有者长时间不释放该锁,那么等待者将长时间的占据cpu时间片,导致CPU资源的浪费,因此可以设定一个时间,当锁持有者超过这个时间不释放锁时,等待者会放弃CPU时间片阻塞;
    2.死锁问题:试想一下,有一个线程连续两次试图获得自旋锁(比如在递归程序中),第一次这个线程获得了该锁,当第二次试图加锁的时候,检测到锁已被占用(其实是被自己占用),那么这时,线程会一直等待自己释放该锁,而不能继续执行,这样就引起了死锁。因此递归程序使用自旋锁应该遵循以下原则:递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁。


    阻塞锁

    让线程进入阻塞状态进行等待,当获得相应的信号(唤醒,时间) 时,才可以进入线程的准备就绪状态,准备就绪状态的所有线程,通过竞争,进入运行状态。。
    JAVA中,能够进入\退出、阻塞状态或包含阻塞锁的方法有 ,synchronized 关键字(其中的重量锁),ReentrantLock,Object.wait()\notify()


    可重入锁

    避免死锁

    可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。
    在JAVA环境下 ReentrantLock 和synchronized 都是 可重入锁


    悲观锁和乐观锁

    悲观锁(Pessimistic Lock), 顾名思义就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。共享锁、排它锁、独占锁是悲观锁的一种实现

    乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号或时间戳等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。使用CAS来保证,保证这个操作的原子性

    乐观锁不是数据库自己实现的

    悲观锁是数据库自己实现的


    两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。


    轮循锁和定时锁

    由tryLock实现,与无条件获取锁模式相比,它们具有更完善的错误恢复机制。可避免死锁的发生:
    boolean tryLock():仅在调用时锁为空闲状态才获取该锁。如果锁可用,则获取锁,并立即返回值 true。如果锁不可用,则此方法将立即返回值 false。


    显示锁和内置锁

    显示锁用Lock来定义、内置锁用syschronized。
    内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
    内置锁是互斥锁


    读写锁

    Lock接口以及对象,使用它,很优雅的控制了竞争资源的安全访问,但是这种锁不区分读写,称这种锁为普通锁。为了提高性能,Java提供了读写锁,在读的地方使用读锁,在写的地方使用写锁,灵活控制,如果没有写锁的情况下,读是无阻塞的,在一定程度上提高了程序的执行效率。
    Java中读写锁有个接口java.util.concurrent.locks.ReadWriteLock,也有具体的实现ReentrantReadWriteLock,详细的API可以查看JavaAPI文档。
    ReentrantReadWriteLock 和 ReentrantLock 不是继承关系,但都是基于 AbstractQueuedSynchronizer 来实现。
    lock方法 是基于CAS 来实现的
    ReadWriteLock中暴露了两个Lock对象:

    在读写锁的加锁策略中,允许多个读操作同时进行,但每次只允许一个写操作。读写锁是一种性能优化的策略。

    RentrantReadWriteLock在构造时也可以选择是一个非公平的锁(默认)还是公平的锁。


    对象锁和类锁

    java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。
    类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的.
    synchronized只是一个内置锁的加锁机制,当某个方法加上synchronized关键字后,就表明要获得该内置锁才能执行,并不能阻止其他线程访问不需要获得该内置锁的方法。

    调用对象wait()方法时,会释放持有的对象锁,以便于调用notify方法使用。notify()调用之后,会等到notify所在的线程执行完之后再释放锁


    锁粗化

    锁粗化的概念应该比较好理解,就是将多次连接在一起的加锁、解锁操作合并为一次,将多个连续的锁扩展成一个范围更大的锁


    互斥锁

    互斥锁, 指的是一次最多只能有一个线程持有的锁。如Java的Lock


    无锁状态-》偏向锁-》轻量级锁-》重量级锁

    锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)。JDK 1.6中默认是开启偏向锁和轻量级锁的,
    锁膨胀:从轻量锁膨胀到重量级锁是在轻量级锁解锁过程发生的。
    重量级锁:Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”。
    轻量级锁:“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的。但是,首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。在解释轻量级锁的执行过程之前,先明白一点,轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。
    偏向锁: 引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。上面说过,轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能。

    无锁状态:在代码进入同步块的时候,如果同步对象锁状态为无锁状态。


    锁消除

    锁消除即删除不必要的加锁操作。根据代码逃逸技术,如果判断到一段代码中,堆上的数据不会逃逸出当前线程,那么可以认为这段代码是线程安全的,不必要加锁。



    展开全文
  • 文章目录1 synchronized的优化1.1 CAS操作1.1.1 CAS带来的ABA问题1.1.2 自旋会浪费大量的处理器资源1.1.3 公平性1.2 Java对象头1.2.1 偏向1.2.1 偏向的获取1.2.1 偏向的撤销1.2.3 偏向的获得和撤销流程 ...


    Synchronized优化:
      在JDK 1.5中,synchronized的性能是很低的,因为这是一个重量级的操作,它对性能最大的影响是阻塞的实现、挂起线程、和恢复线程都需要转入内核态完成,这些给操作系统的并发性带来了很大的压力。
      但是在JDK1.6,发生了变化,对synchronized加入了很多优化措施,
      之前我们已经对synchronized有一定的了解,它最大的特征就是在同一时刻只有一个线程能够获得对象的监视器(monitor),从而2进入到同步代码块或者同步方法中,即表现为互斥性(排它性)。这种方式效率低下,每次只能通过一个线程,既然每次都只能通过一个,如果这种形式不能改变的话,那么我们能不能让每次通过的速度变快一点呢?
      比如:去收银台付款,之前的方式是大家都去排队,然后取纸币付款收银员找零,付款的时候需要在包里拿出钱包再拿出钱,这个过程是比较耗时的,然后支付宝解放了大家去钱包找钱的过程,现在只需要扫描二维码就可以完成付款,也省去了收银员找零的时间,同样是需要排队付款,但整个付款的时间大大缩短,整体效率也变快了。这种优化同样可以引申到锁优化上,缩短获取锁的时间。
    在锁优化之前,需要关注两个知识点(1)CAS操作 (2)Java对象头

    1. CAS操作

    1.1 什么是CAS?

      在使用锁时,线程获取锁是一种悲观锁策略,即假设每一次执行临界区代码都会产生冲突,所以当前线程获取到锁的时候同时也会阻塞其他线程获取该锁。而CAS操作是一种乐观锁策略,它假设所有线程访问共享资源的时候不会出现冲突,既然不会出现冲突自然而然就不会阻塞其他线程的操作。因此,线程就不会出现阻塞停顿的状态,那么这种情况下出现冲突了怎么办?无锁操作是使用CAS(compare and swap)又叫做比较交换来鉴别线程是否出现冲突,出现冲突就重试当前操作直到没有冲突为止。

    1.1.1 CAS的操作过程

    CAS可以通俗的理解为CAS(V,O,N)其中包含三个值分别是
    V:内存地址中实际存放的值;
    O:预期的值;
    N:更新后的值。

      当V==O相同时,也就是说预期值和内存中实际的值相同,表明该值没有被其他线程更改过,即预期值O就是目前来说最新的值了,可以将N赋给V。
      当V!=O不相同,表明V值已经被其他线程改过了,即预期值0不是最新值了,所以不能将新值N赋给V,返回V值,将O值改为V。

      当多个线程使用CAS操作一个变量时,只有一个线程会成功并成功更新,其余会失败,失败的线程会重新尝试(自旋),当然也可以选择挂起线程(阻塞)。

    未优化的synchronized最主要的问题是:
       在存在线程竞争的情况下会出现线程阻塞和唤醒锁带来的性能问题,这是一种互斥同步(阻塞同步)
      而CAS并不是武断的将线程挂起,当CAS操作失败后会进行一定的尝试,而非进行耗时的挂起唤醒的操作,因此也叫做非阻塞同步。

    1.1.2 CAS带来的问题

    1.1.2.1 CAS带来的ABA问题

      线程1准备用CAS将变量的值由A替换为B,在此之前,线程2将变量的值由A替换为C,又由C替换为A,然后线程1执行CAS时发现变量的值仍然为A,所以CAS成功。但实际上这时的现场已经和最初不同了。
      例如一个单向链表:
    在这里插入图片描述
    解决方案:
      atomic包中提供了AtomicStampedReference来解决ABA问题相当于添加了版本号

    1.1.2.2 自旋会浪费大量的处理器资源

      与线程阻塞相比,自旋会浪费大量的处理器资源。这是因为当前线程仍处于运行状况,只不过跑的是无用指令。它期望在运行无用指令的过程中,锁能够被释放出来。

      例如:阻塞相当于熄火停车,自旋状态相当于怠速停车。在十字路口,如果红绿灯等待的时间非常长,那么熄火相对省油一些;如果红绿灯的等待时间非常短,怠速停车更合适。

      然而,对于JVM来说,它并不能看到红灯的剩余时间,也就没法根据等待时间的长短来选择是自旋还是阻塞,JVM给出的方案是自适应自旋,根据以往自旋等待时能否获取锁,来动态调整自旋的时间。即就是如果在自旋的时候获取到锁,则会增加下一次自旋的时间,否则就稍微减小下一次自旋时长,对于我们的例子就是:如果之前不熄火等待了绿灯,那么这次不熄火的时间就长一点;如果之前不熄火没等待绿灯,那么这次不熄火的时间就短一点。

    1.1.2.3 CAS带来的公平性问题

      自旋状态还带来另外一个副作用,不公平的锁机制。处于阻塞状态的线程,无法立刻竞争被释放的锁。然而处于自旋状态的线程,则很有可能优先获得这把锁。内建锁无法实现公平机制
    Lock锁可以

    2. Java对象头

      同步的时候是获取对象的monitor,即获取到对象的锁,对象的锁无非就是类似对对象的一个标志,这个标志就是存在Java对象的对象头中。Java对象头里的Mark Word里(markword是java对象数据结构中的一部分)默认存放是的对象的Hashcode,分代年龄和锁标记位。32位JVM对象头中 Mark Word默认存储结构为:

    锁状态 25bit 4bit 1bit是否是偏向锁 2bit锁标志位
    无锁状态 对象的hashCode 对象分代年龄 0 01

      Java SE 1.6中,锁一共有四种状态,级别从低到高依次是:无锁状态(0 01),偏向锁状态(1 01),轻量级锁状态(00)和重量级锁状态(10),这几个状态会随着竞争情况逐渐升级,锁可以升级但是不可以降级(为了提高获得锁和释放锁的效率)

    3. 偏向锁

      大多数情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。JDK1.6之后 偏向锁默认开启
    偏向锁是锁状态中最乐观的一种锁:从始至终只有一个线程请求同一把锁
    偏向锁对象头的Mark Word:

    偏向锁 线程ID Epoch 对象分代年龄 1 01

    3.1 偏向锁的获取

    偏向锁的获取过程:
      当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后在该线程进入和退出同步块时不需要CAS操作来加锁和解锁,只需要简单的测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需要再测试一下Mark Word中偏向锁的标志是否设置成1,如果没有设置,使用CAS竞争锁,如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。

    3.2 偏向锁的撤销

    偏向锁的撤销过程:
      偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。
    在这里插入图片描述

    3.3 偏向锁的获取和撤销流程

    在这里插入图片描述

    4. 轻量级锁

      多个线程在不同时刻请求同一把锁,也就是不存在锁竞争的情况。针对这种状况,JVM采用了轻量级锁来避免线程的阻塞与唤醒
    轻量级锁的Mark Word:

    轻量级锁 指向栈中锁记录的指针 00

    4.1 轻量级锁的获取

      线程在执行同步块之前,JVM会先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获取到锁。如果失败,表示其他线程竞争锁。

    4.2 轻量级锁的撤销

      轻量级锁解锁时,会使用原子的CAS操作将Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁(多个线程在相同时刻竞争同一把锁)。
    在这里插入图片描述

    5. 三种锁的特点

    偏向锁
      偏向锁只会在第一次请求锁时使用CAS操作,并在锁对象的标记字段中记录当前线程ID。在此后的运行过程中,持有偏向锁的线程无需加锁操作。针对的是锁仅会被同一线程持有的状况。
    轻量级锁
      轻量级锁采用CAS操作,将对象头中的Mark Word替换为指向锁记录的指针。针对的是多个线程在不同时间段申请同一把锁的情况。
    重量级锁
      重量级锁会阻塞、唤醒请求加锁的线程。针对的是多个线程同时竞争同一把锁的情况。JVM采用自适应自旋,来避免在面对非常小的同步代码块时,仍会被阻塞和唤醒的状况。

    6.锁粗化

      锁粗化就是将多次连接在一起的加锁、解锁操作合并为一次操作。将多个联系的锁扩展为一个范围更大的锁。

    public class Test{
        //全局变量有线程安全问题
        public static StringBuffer sb = new StringBuffer();
        public static void main(String[] args) {
            sb.append("a");
            sb.append("b");
            sb.append("c");
        }
    }
    

    在这里插入图片描述
    每次调用StringBuffer的append方法都需要加锁和解锁,如果虚拟机检测到有一系列连串的对同一对象加锁和解锁操作,就会在在第一次append方法时进行加锁,在最后一次append方法结束后进行解锁。

    7.锁消除

      删除不必要的加锁操作,如果判断一段代码中,堆上的数据不会逃逸出当前线程,则认为此代码是线程安全的,无需加锁

    public class Test{
        public static void main(String[] args) {
            //sb变量是局部变量,不会有线程安全问题,加锁、解锁没有意义
            StringBuffer sb = new StringBuffer();
            sb.append("a");
            sb.append("b");
            sb.append("c");
        }
    }
    

    此时虽然append是同步方法,但是这段程序中StringBuffer属于一个局部变量,即每个线程进入此方法都会拥有一个StringBuffer的变量,互不影响,线程安全

    展开全文
  • 偏向锁,轻量级锁与重量级锁的区别与膨胀

    万次阅读 多人点赞 2017-03-19 20:44:01
    一直被这三个锁的膨胀问题所困扰,不知道到底实在什么时候会有偏向锁升级到轻量级锁,什么时候由轻量级锁升级到重量级锁。找到好久,也没有找到简洁明了的答案。  综合多个方面的描述综合自己的理解,特地记录下来...
  • Java对象锁的理解

    万次阅读 热门讨论 2018-07-16 14:57:27
    以前理解Java的锁机制认为:锁的类型分为‘类锁’,’方法锁‘,’对象锁‘。 1.类锁(synchronize(静态对象)):类的所有对象都要竞争锁。 2.方法锁(在方法前加synchronize): 同一对象同一方法需要竞争锁。 3....
  • 面试必备之乐观锁与悲观锁

    万次阅读 多人点赞 2018-07-16 22:34:26
    何谓悲观锁与乐观锁 乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人。 悲观...
  • Java synchronized之类锁/对象锁

    千次阅读 2017-11-03 18:24:15
    对象锁是基于对堆内存内对象的头部加锁信息; 类锁是基于对类对应的 java.lang.Class对象加所信息; 特别的, synchronized(this) 是对this所对应的对象加锁。需要特别注意的事是, 根据JMM的规范, synchronized 块...
  • 对象锁共有四种状态:无锁、偏向锁、轻量级锁、重量级锁,锁竞争程度依次加重。 对象锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。 自旋是一种获取锁的策略,存在于获取轻量级锁的过程中...
  • 轻量级锁与偏向锁

    万次阅读 2015-04-18 20:30:42
    要了解轻量级锁与偏向锁的原理和运作过程,需要先了解Hotspot虚拟机的对象头部分的内存布局。 1. 对象对象自身的运行时数据 如:哈希吗(HashCode)、GC分代年龄(Generational GC Age)等,这部分数据的长度...
  • 对象锁和类锁的区别

    千次阅读 2020-04-12 16:47:25
    对象锁和类锁的区别synchronized 关键字对象锁1、锁住 this 对象2、 锁住实体里的非静态变量3、直接锁非静态方法对象锁代码测试类锁1、锁住 xxx.class2、锁住类中的静态变量3、直接在静态方法上加 synchronized类锁...
  • 锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)
  • (1)基础Java中的每一个对象都可以作为。对于同步方法,是当前实例对象。对于静态同步方法,是当前对象的Class对象。对于同步方法块,是Synchonized括号里配置的对象。 当一个线程试图访问同步代码块时,...
  • java中每个对象都可作为锁,锁有四种级别,按照量级从轻到重分为:无锁、偏向锁、轻量级锁、重量级锁。并且锁只能升级不能降级。 在讲这三个锁之前,我先给大家讲清楚自旋和对象头的概念。 自旋 现在假设有这么一...
  • 前面讲到了重量级锁是悲观锁的一种,自旋锁、轻量级锁与偏向锁属于乐观锁,所以现在你就能够大致理解了他们的适用范围,但是具体如何使用这几种锁呢,就要看后面的具体分析他们的特性; java中的锁 自旋锁...
  • 偏向锁、轻量级锁、重量级锁解析

    千次阅读 2021-02-19 21:22:17
    Java 对象头 在32位虚拟机和64位虚拟机的 Mark Word 所占用的字节大小不一样,32位虚拟机的 Mark Word 和 class Pointer ...偏向锁、轻量级锁的状态转化及对象Mark Word的关系 偏向锁的获得和撤销流程 时序图 流程图
  • 轻量级锁、偏向锁、重量级锁详情

    千次阅读 2018-11-17 13:48:47
    这篇文章是上篇文章是否真的理解了偏向锁、轻量级锁、重量级锁(锁膨胀)、自旋锁、锁消除、锁粗化,知道重偏向吗?的补充,对于偏向锁,网上有些对于它的原理解读过于简单,简单得似乎是错误的,最显眼的是对于Mark...
  • 在多线程并发编程中Synchronized一直是元老级角色,很多人都会称呼它为重量级锁,但是随着Java SE1.6对Synchronized进行了各种优化之后,有些情况下它并不那么重了,Java SE1.6中为了减少获得锁和释放锁带来的性能...
  • Java--偏向锁/轻量级锁/重量级锁

    千次阅读 2018-04-10 14:34:01
    另外偏向锁---轻量级锁---重量级锁本文都需要依托synchronize进行理解和分析。另外也要参照网络上很多的资料。1.对象头:关于对象头的具体记录,可以参考这边:对象头2.同步的原理:关于JVM规范对于同步的解释,可以...
  • JVM系列之:对象状态和同步

    万次阅读 2020-07-24 09:14:07
    文章目录简介java对象头java中锁状态的变化偏向锁biased locking轻量级锁thin lock重量级锁三种锁状态的不同 简介 锁和同步是java多线程编程中非常常见的使用场景。为了锁定多线程共享的对象,Java需要提供一定的...
  • jdk1.6之后,因为线程状态间的切换低效,所以对synchronized实现同步功能进行了优化,引入偏向锁,轻量级锁,自旋锁和重量级锁等概念,来提高synchronized实现同步锁的效率。 synchronized获取不到锁之后,不会立即...
  • 一、简介 在讲解这些概念之前,我们要明确的是这些不等同于Java API中的ReentratLock这种,这些是概念上的,是JDK1.6中为了对...二、Java对象头中的Mark WordHotSpot中,Java的对象内存模型分为...
  • JDK1.6为了减少获得锁和释放锁所带来的性能消耗,引入了“偏向锁”和“轻量级锁”,所以在JDK1.6里锁一共有四种状态,无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,它会随着竞争情况逐渐升级。锁可以升级但不...
  • 为什么出现CAS? 我们知道synchronized关键字不论是实现同步方法还是同步代码块,都需要首先执行...monitor机制是JDK1.6之前synchronized(内建锁)底层原理,又称为JDK1.6重量级锁。线程的阻塞和唤醒均...
  • 偏向锁,轻量级锁,重量级锁

    千次阅读 2016-07-06 20:31:03
    偏向锁,轻量级锁,重量级锁标签(空格分隔): java 并发 锁偏向锁偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在接下来的运行过程中,该锁没有被其他的线程访问,则持有偏向锁的线程将永远不需要触发...
  • 采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其他线程想再获取这个【对象锁】时就会阻塞住,这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心上下文切换。 synchronized实际是用...
  • 五,轻量级锁 六,自旋锁 SpinLock 七,重量级锁 八,在应用层提高锁效率的方案 一,概述 什么是java的锁? 为什么java要有锁? java的锁为什么需要优化? 怎么优化的? 1,java中使用synchronized关键字...
  • 其实在JDK1.5以前的早期版本,还没有那么细粒度完善的锁机制,基本上就一个synchronized打遍天下,但是从JDK1.6之后Oracle对Java锁进行了很大的改动,也就出现了偏向锁/轻量级锁机制和锁的升级/降级机制 偏向锁和轻...
  • (面试题)讲下偏向锁、自旋锁、轻量级锁、重量级锁 一、自旋锁 如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等...
  • 如果持有的线程能在很短时间内释放资源,那么那些等待竞争的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,只需让线程执行一个忙循环(自旋),等持有的线程释放后即可立即获取,这样就避免...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 195,102
精华内容 78,040
关键字:

对象锁与类锁