精华内容
下载资源
问答
  • Java 并发锁机制

    2021-06-08 21:20:12
    并发编程中,我们常常会分不清各种锁机制,本文我们详细介绍了几种Java中常用的锁概念,与实例。

    Java中的锁机制

    1. 乐观锁与悲观锁

    乐观锁:不加锁,通过版本号进行控制

    悲观锁:加锁

    2. 公平锁非公平锁

    公平锁:排队一个一个来

    ReentrantLock(false)

    非公平锁:容许插队,性能更高,默认都是非公平锁

    synchronized、ReentrantLock

    3. 独享锁/共享锁:

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

    4. 可重入锁

    ReentrantLock 和 synchronized 都是可重入锁

    5. 分段锁

    分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。

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

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

    这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。

    偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。

    • 偏向锁的适用场景

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

    • 轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。

    • 重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

    7. 读写锁

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

    • 读-读 可共存
    • 读-写 不可共存
    • 写-写 不可共存
    // 资源类
    class MyCache{
        private volatile Map<String,Object> map = new HashMap<>();
        private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        public void put(String key, Object value){
            lock.writeLock().lock();
            System.out.println("正在写入:" + key);
            try {
                TimeUnit.MILLISECONDS.sleep(300);
                map.put(key, value);
                System.out.println("写入完成!!!");
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                lock.writeLock().unlock();
            }
        }
        public void get(String key){
            lock.readLock().lock();
            System.out.println("正在读取:" + key);
            try {
                TimeUnit.MILLISECONDS.sleep(100);
                Object o = map.get(key);
                System.out.println("读取完成,结果:" + o);
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                lock.readLock().unlock();
            }
        }
        public void clear(){
            map.clear();
        }
    }
    
    /**
     * @author zhj
     */
    public class ReadWriteLockDemo {
        /**
         * 读写锁  ---------读写分离,提高并发能力
         * 读-读可共存
         * 读-写不可共存
         * 写-写不可共存
         * @param args
         */
        public static void main(String[] args) {
            MyCache myCache = new MyCache();
            for (int i = 0; i < 5; i++) {
                final int tempInt = i;
                new Thread(()->{
                    myCache.put(tempInt+"",tempInt+"");
                },"w" + i).start();
            }
    
            for (int i = 0; i < 5; i++) {
                final int tempInt = i;
                new Thread(()->{
                    myCache.get(tempInt+"");
                },"r" + i).start();
            }
            myCache.clear();
        }
    }
    

    8. 自旋锁

    利用循环进行判断是否加锁,基于CAS(比较并交换)

    public class SpinLockDemo {
    
        // 原子引用线程
        AtomicReference<Thread> atomicReference = new AtomicReference<>();
    
        public void myLock(){
            Thread thread = Thread.currentThread();
            System.out.println(thread.getName() + "\t come in O(n_n)O");
    
            while (!atomicReference.compareAndSet(null,thread)){
    
            }
        }
    
        public void myUnlock(){
            Thread thread = Thread.currentThread();
            atomicReference.compareAndSet(thread,null);
            System.out.println(thread.getName() + "\t invoked myUnlock");
        }
    
        public static void main(String[] args) {
            SpinLockDemo spinLockDemo = new SpinLockDemo();
            new Thread(()->{
                try {
                    spinLockDemo.myLock();
                    TimeUnit.SECONDS.sleep(5);
                } catch (Exception e){
                    e.printStackTrace();
                } finally {
                    spinLockDemo.myUnlock();
                }
            },"T1").start();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e){
                e.printStackTrace();
            }
            new Thread(()->{
                try {
                    spinLockDemo.myLock();
                    TimeUnit.SECONDS.sleep(3);
                } catch (Exception e){
                    e.printStackTrace();
                } finally {
                    spinLockDemo.myUnlock();
                }
            },"T2").start();
        }
    }
    
    展开全文
  • 对于我们开发的网站,如果网站的访问量非常大的话,那么我们就需要考虑相关的并发访问问题了。而并发问题是绝大部分的程序员头疼的问题,但话又说回来了,既然逃避不掉,那我们就坦然面对吧~今天就让我们一起来研究...

    对于我们开发的网站,如果网站的访问量非常大的话,那么我们就需要考虑相关的并发访问问题了。而并发问题是绝大部分的程序员头疼的问题,

    但话又说回来了,既然逃避不掉,那我们就坦然面对吧~今天就让我们一起来研究一下常见的并发和同步吧。

    为了更好的理解并发和同步,我们需要先明白两个重要的概念:同步和异步

    1、同步和异步的区别和联系

    所谓同步,可以理解为在执行完一个函数或方法之后,一直等待系统返回值或消息,这时程序是出于阻塞的,只有接收到

    返回的值或消息后才往下执行其它的命令。

    异步,执行完函数或方法后,不必阻塞性地等待返回值或消息,只需要向系统委托一个异步过程,那么当系统接收到返回

    值或消息时,系统会自动触发委托的异步过程,从而完成一个完整的流程。

    同步在一定程度上可以看做是单线程,这个线程请求一个方法后就待这个方法给他回复,否则他不往下执行(死心眼)。

    异步在一定程度上可以看做是多线程的(废话,一个线程怎么叫异步),请求一个方法后,就不管了,继续执行其他的方法。

    同步就是一件事,一件事情一件事的做。

    异步就是,做一件事情,不引响做其他事情。

    例如:吃饭和说话,只能一件事一件事的来,因为只有一张嘴。

    但吃饭和听音乐是异步的,因为,听音乐并不引响我们吃饭。

    对于Java程序员而言,我们会经常听到同步关键字synchronized,假如这个同步的监视对象是类的话,那么如果当一个对象

    访问类里面的同步方法的话,那么其它的对象如果想要继续访问类里面的这个同步方法的话,就会进入阻塞,只有等前一个对象

    执行完该同步方法后当前对象才能够继续执行该方法。这就是同步。相反,如果方法前没有同步关键字修饰的话,那么不同的对象

    可以在同一时间访问同一个方法,这就是异步。

    在补充一下(脏数据和不可重复读的相关概念):

    脏数据

    脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这 个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据(Dirty Data),依据脏数据所做的操作可能是不正确的。

    不可重复读

    不可重复读是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读

    2、如何处理并发和同步

    今天讲的如何处理并发和同同步问题主要是通过锁机制。

    我们需要明白,锁机制有两个层面。

    一种是代码层次上的,如java中的同步锁,典型的就是同步关键字synchronized,这里我不在做过多的讲解,

    另外一种是数据库层次上的,比较典型的就是悲观锁和乐观锁。这里我们重点讲解的就是悲观锁(传统的物理锁)和乐观锁。

    悲观锁(Pessimistic Locking):

    悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自 外部系统的事务处理)修改持保守态度,因此,

    在整个数据处理过程中,将数据处于锁定状态。

    悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能 真正保证数据访问的排他性,否则,即使在本系统

    中实现了加锁机制,也无法保证外部系 统不会修改数据)。

    一个典型的倚赖数据库的悲观锁调用:

    select * from account where name=”Erica” for update

    这条 sql 语句锁定了 account 表中所有符合检索条件( name=”Erica” )的记录。

    本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。

    Hibernate 的悲观锁,也是基于数据库的锁机制实现。

    下面的代码实现了对查询记录的加锁:

    String hqlStr =”from TUser as user where user.name=’Erica'”;

    Query query = session.createQuery(hqlStr);

    query.setLockMode(“user”,LockMode.UPGRADE); // 加锁

    List userList = query.list();// 执行查询,获取数据

    query.setLockMode 对查询语句中,特定别名所对应的记录进行加锁(我们为 TUser 类指定了一个别名 “user” ),这里也就是对

    返回的所有 user 记录进行加锁。

    观察运行期 Hibernate 生成的 SQL 语句:

    select tuser0_.id as id, tuser0_.name as name, tuser0_.group_id

    as group_id, tuser0_.user_type as user_type, tuser0_.sex as sex

    from t_user tuser0_ where (tuser0_.name=’Erica’ ) for update

    这里 Hibernate 通过使用数据库的 for update 子句实现了悲观锁机制。

    Hibernate 的加锁模式有:

    Ø LockMode.NONE : 无锁机制。

    Ø LockMode.WRITE : Hibernate 在 Insert 和 Update 记录的时候会自动获取

    Ø LockMode.READ : Hibernate 在读取记录的时候会自动获取。

    以上这三种锁机制一般由 Hibernate 内部使用,如 Hibernate 为了保证 Update

    过程中对象不会被外界修改,会在 save 方法实现中自动为目标对象加上 WRITE 锁。

    Ø LockMode.UPGRADE :利用数据库的 for update 子句加锁。

    Ø LockMode. UPGRADE_NOWAIT : Oracle 的特定实现,利用 Oracle 的 for

    update nowait 子句实现加锁。

    上面这两种锁机制是我们在应用层较为常用的,加锁一般通过以下方法实现:

    Criteria.setLockMode

    Query.setLockMode

    Session.lock

    注意,只有在查询开始之前(也就是 Hiberate 生成 SQL 之前)设定加锁,才会

    真正通过数据库的锁机制进行加锁处理,否则,数据已经通过不包含 for update

    子句的 Select SQL 加载进来,所谓数据库加锁也就无从谈起。

    为了更好的理解select… for update的锁表的过程,本人将要以mysql为例,进行相应的讲解

    1、要测试锁定的状况,可以利用MySQL的Command Mode ,开二个视窗来做测试。

    表的基本结构如下:

    qQvQZb.png

    表中内容如下:

    eA3iay.png

    开启两个测试窗口,在其中一个窗口执行select * from ta for update0

    然后在另外一个窗口执行update操作如下图:

    qQ7Fzu.png

    等到一个窗口commit后的图片如下:

    M7bqQz.png

    到这里,悲观锁机制你应该了解一些了吧~

    需要注意的是for update要放到mysql的事务中,即begin和commit中,否者不起作用。

    至于是锁住整个表还是锁住选中的行,请参考:

    至于hibernate中的悲观锁使用起来比较简单,这里就不写demo了~感兴趣的自己查一下就ok了~

    乐观锁(Optimistic Locking):

    相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依 靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之

    而来的就是数据库 性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。 如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数

    据的基础上进 行修改时(如更改用户帐户余额),如果采用悲观锁机制,也就意味着整个操作过 程中(从操作员读出数据、开始修改直至提交修改结果的全

    过程,甚至还包括操作 员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对几 百上千个并发,这样的情况将导致怎样的后果。 乐

    观锁机制在一定程度上解决了这个问题。

    乐观锁,大多是基于数据版本   Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通

    过为数据库表增加一个 “version” 字段来 实现。 读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提 交数据的版本数据与数据

    库表对应记录的当前版本信息进行比对,如果提交的数据 版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。对于上面修改用户帐户信息

    的例子而言,假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。操作员 A 此时将其读出

    ( version=1 ),并从其帐户余额中扣除 $50( $100-$50 )。 2 在操作员 A 操作的过程中,操作员 B 也读入此用户信息( version=1 ),并 从其帐

    户余额中扣除 $20 ( $100-$20 )。 3 操作员 A 完成了修改工作,将数据版本号加一( version=2 ),连同帐户扣 除后余额( balance=$50 ),提交

    至数据库更新,此时由于提交数据版本大 于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。 4 操作员 B 完成了操作,也将版本号加一

    ( version=2 )试图向数据库提交数 据( balance=$80 ),但此时比对数据库记录版本时发现,操作员 B 提交的 数据版本号为 2 ,数据库记录当前版

    本也为 2 ,不满足 “ 提交版本必须大于记 录当前版本才能执行更新 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。 这样,就避免了操作员 B 用基于

    version=1 的旧数据修改的结果覆盖操作 员 A 的操作结果的可能。 从上面的例子可以看出,乐观锁机制避免了长事务中的数据库加锁开销(操作员 A

    和操作员 B 操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系 统整体性能表现。 需要注意的是,乐观锁机制往往基于系统中的数据存储

    逻辑,因此也具备一定的局 限性,如在上例中,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户 余额更新操作不受我们系统的控制,因此可能

    会造成脏数据被更新到数据库中。在 系统设计阶段,我们应该充分考虑到这些情况出现的可能性,并进行相应调整(如 将乐观锁策略在数据库存储过程中实

    现,对外只开放基于此存储过程的数据更新途 径,而不是将数据库表直接对外公开)。

    展开全文
  • 同步的基本思想为了保证共享数据在同一时刻只被一个线程使用,我们有一种很简单的实现思想,就是在共享数据里保存一个,当没有线程访问时,是空的。当有第一个线程访问时,就在里保存这个线程的标识并允许这个...

    同步的基本思想

    为了保证共享数据在同一时刻只被一个线程使用,我们有一种很简单的实现思想,就是

    在共享数据里保存一个锁 ,当没有线程访问时,锁是空的。

    当有第一个线程访问时,就 在锁里保存这个线程的标识 并允许这个线程访问共享数据。

    在当前线程释放共享数据之前,如果再有其他线程想要访问共享数据,就要 等待锁释放 。

    在共享数据里保存一个锁

    在锁里保存这个线程的标识

    其他线程访问已加锁共享数据要等待锁释放

    Jvm同步的实现

    jvm中有以下三种锁(由上到下越来越“重量级”):

    偏向锁

    轻量级锁

    重量级锁

    重量级锁

    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等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

    Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的。

    但是监视器锁本质又是依赖于底层的操作系统的互斥锁(Mutex Lock)来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。

    因此,这种依赖于操作系统互斥锁(Mutex Lock)所实现的锁我们称之为“重量级锁”。

    轻量级锁

    锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。

    JDK 1.6中默认是开启偏向锁和轻量级锁的,我们也可以通过-XX:-UseBiasedLocking来禁用偏向锁。

    轻量级锁的核心思想就是“被加锁的代码不会发生并发,如果发生并发,那就膨胀成重量级锁(膨胀指的锁的重量级上升,一旦升级,就不会降级了)”。

    轻量级锁依赖了一种叫做CAS(compare and swap)的操作。参考http://ifeve.com/compare-and-swap/

    术语定义

    术语

    英文

    说明

    CAS

    Compare and Swap

    比较并设置。

    用于在硬件层面上提供原子性操作。

    在 Intel 处理器中,比较并交换通过指令cmpxchg实现。比较是否和给定的数值一致,如果一致则修改,不一致则不修改。

    偏向锁

    根据轻量级锁的实现,我们知道虽然轻量级锁不支持“并发”,遇到“并发”就要膨胀为重量级锁,但是轻量级锁可以支持多个线程以串行的方式访问同一个加锁对象。

    比如A线程可以先获取对象o的轻量锁,然后A释放了轻量锁,这个时候B线程来获取o的轻量锁,是可以成功获取得,以这种方式可以一直串行下去。

    之所以能实现这种串行,是因为有一个释放锁的动作。那么假设有一个加锁的java方法,这个方法在运行的时候其实从始至终只有一个线程在调用,但是每次调用完却也要释放锁,下次调用还要重新获得锁。

    那么我们能不能做一个假设:“假设加锁的代码从始至终就只有一个线程在调用,如果发现有多于一个线程调用,再膨胀成轻量级锁也不迟”。这个假设,就是偏向锁的核心思想。

    偏向锁依赖了一种叫做CAS(compare and swap)的操作。

    总结

    本文重点介绍了JDk中采用轻量级锁和偏向锁等对Synchronized的优化,

    但是这两种锁也不是完全没缺点的,比如竞争比较激烈的时候,不但无法提升效率,反而会降低效率,因为多了一个锁升级的过程,这个时候就需要通过-XX:-UseBiasedLocking来禁用偏向锁。下面是这几种锁的对比:

    优点

    缺点

    适用场景

    偏向锁

    加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。

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

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

    轻量级锁

    竞争的线程不会阻塞,提高了程序的响应速度。

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

    追求响应时间。

    同步块执行速度非常快。

    重量级锁

    线程竞争不使用自旋,不会消耗CPU。

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

    追求吞吐量。

    同步块执行速度较长。

    命令介绍:

    jps -m 打印所有的java应用程序id,如可以用该命令查询死锁的id。

    jstack id 可以查询应用程序的详细信息。

    解决死锁的思路方法:

    1.可以对竞争的参数排序。,如下图

    568302a9498ca010553bc0feb09ff27b.png

    2。一次性拿到所有资源,否则就都放弃。ReentrantLock.

    65b8a712be1bc9c751a2cefabfb14fd3.png

    a0d3af07c9bc027744666349528ac50c.png

    解决活锁的思路:如上面第2条加入Thread.sleep(r.nextInt(10))。

    线程饥饿的解决思路:

    参考如下:

    java命令--jstack 工具

    展开全文
  • 本文着重介绍了在java并发中常见的几种锁机制。1.偏向锁偏向锁是JDK1.6提出来的一种锁优化的机制。其核心的思想是,如果程序没有竞争,则取消之前已经取得锁的线程同步操作。也就是说,若某一锁被线程获取后,便进入...

    随着互联网的蓬勃发展,越来越多的互联网企业面临着用户量膨胀而带来的并发安全问题。本文着重介绍了在java并发中常见的几种锁机制。

    1.偏向锁

    偏向锁是JDK1.6提出来的一种锁优化的机制。其核心的思想是,如果程序没有竞争,则取消之前已经取得锁的线程同步操作。也就是说,若某一锁被线程获取后,便进入偏向模式,当线程再次请求这个锁时,就无需再进行相关的同步操作了,从而节约了操作时间,如果在此之间有其他的线程进行了锁请求,则锁退出偏向模式。在JVM中使用-XX:+UseBiasedLocking

    package jvmProject;

    import java.util.List;

    import java.util.Vector;

    public class Biased {

    public static List numberList = new Vector();

    public static void main(String[] args) {

    long begin = System.currentTimeMillis();

    int count = 0;

    int startnum = 0;

    while(count<10000000){

    numberList.add(startnum);

    startnum+=2;

    count++;

    }

    long end = System.currentTimeMillis();

    System.out.println(end-begin);

    }

    }

    初始化一个Vector,往里面添加10000000个Integer对象,然后输出时间差。以此来测试偏向锁的性能。至于为什么要使用Vector而不使用ArrayList呢?

    因为ArrayList是线程不安全的,Vector是线程安全的。这样说可能还不够具体,可以翻看一下源码吧。

    ace935669d0731bb31c7eeef28b43ad2.png

    8f6f3b32c44dec66a2282391a777725d.png

    Vector中的几乎所有操作是带有sychronized的,而ArrayList是没有的,所以Vector是线程安全的。

    接下来我们来测试一下,开启偏向锁和不开启偏向锁对程序性能的影响有多大。

    配置JVM启动(开启偏向锁)参数为:

    ec52b33a8e3944c65d3d83e0526fec2a.png

    3784dc782b6d5438ddf3e52b4b71525c.png

    配置JVM启动(关闭偏向锁)参数为:

    c33ff2edc39f701ae953eec66f2a12a2.png

    d4631e482899ee2aa22da20746abf920.png

    Perfect!开启偏向锁的程序运行时间明显较短,开启偏向锁比不开启偏向锁,在单个线程中操作一个对象的同步方法,是有一定的优势的。其实也可以这样理解,当只有一个线程操作带有同步方法的Vector对象的时候,此时对Vector的操作就转变成了对ArrayList的操作。

    偏向锁在锁竞争激烈的场合没有太强的优化效果,因为大量的竞争会导致持有锁的线程不停地切换,锁也很难保持在偏向模式,此时,使用偏向锁不仅得不到性能的优化,反而有可能降低系统的性能,因此,在激烈竞争的场合,可以尝试使用

    -XX:-UseBiastedLocking参数禁用偏向锁。

    2.轻量级锁

    如果偏向锁失败,Java虚拟机就会让线程申请轻量级锁,轻量级锁在虚拟机内部,使用一个成为BasicObjectLock的对象实现的,这个对象内部由一个BasicLock对象和一个持有该锁的Java对象指针组成。BasicObjectLock对象放置在Java栈帧中。在BasicLock对象内部还维护着displaced_header字段,用于备份对象头部的Mark Word.

    当一个线程持有一个对象的锁的时候,对象头部Mark Word信息如下

    [ptr |00] locked

    末尾的两位比特为00,整个Mark Word为指向BasicLock对象的指针。由于BasicObjectLock对象在线程栈中,因此该指针必然指向持有该锁的线程栈空间。当需要判断一个线程是否持有该对象时,只需要简单地判断对象头的指针是否在当前线程的栈地址范围即可。同时,BasicLock对象的displaced_header,备份了原对象的Mark word内容,BasicObjectLock对象的obj字段则指向持有锁的对象头部。

    3.重量级锁

    当轻量级锁失败,虚拟机就会使用重量级锁。在使用重量级锁的时,对象的Mark Word如下:

    [ptr |10] monitor

    重量级锁在操作过程中,线程可能会被操作系统层面挂起,如果是这样,线程间的切换和调用成本就会大大提高。

    4.自旋锁

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

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

    在JDK1.6中,Java虚拟机提供-XX:+UseSpinning参数来开启自旋锁,使用-XX:PreBlockSpin参数来设置自旋锁等待的次数。

    在JDK1.7开始,自旋锁的参数被取消,虚拟机不再支持由用户配置自旋锁,自旋锁总是会执行,自旋锁次数也由虚拟机自动调整。

    展开全文
  • java锁机制

    2021-03-04 00:22:41
    一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在java里边就是拿到某个同步对象的(一个对象只有一把);如果这个时候同步对象的被其他线程拿走了,他(这个线程)就只能等了(线程...
  • 深入理解 Java 锁机制

    2021-03-13 01:53:05
    Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock。一、synchronized说起 Java 中的锁,第一反应就是 synchronized,我们可以...
  • 当我们在使用Java进行网络编程时经常会遇到很多...而对于Java并发来说,与超时相关的内容主要是线程等待超时和获取超时,比如调用Object.wait(long)就会使线程进入等待状并在指定时间后等待超时。此篇主要讲解Java...
  • Java并发机制的底层实现原理Java代码在编译后会变成字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令。volatile--定义...
  • Java并发——显示

    2020-12-24 08:54:08
    Java提供一系列的显示类,均位于java.util.concurrent.locks包中。的分类: 排他,共享排他又被称为独占,即读写互斥、写写互斥、读读互斥。Java的ReadWriteLock是一种共享,提供读读共享,但读写和写...
  • 多线程并发访问同一个资源问题,假如线程A获取变量之后修改变量值,线程C在此时也获取变量值并且修改,两个线程同时并发处理一个变量,就会导致并发问题。 这种并行处理数据库的情况在实际的业务开发中很常见,两个...
  • Java 8 锁机制

    2021-02-26 20:40:03
    主要对Java 8 常用的如何使用进行分享一、synchronized(一)、用法:1.synchronized可以用在方法(包含静态方法),2.synchronized块void increment() {synchronized (this) {count += 1;}}(二)、原理:关键词:monitor1....
  • 一、绪论在JAVA一共有四种状态:无锁状态、偏向状态、轻量级状态和重量级状态(按从低到高顺序,着竞争情况逐渐升级)JAVA只能升级却不能降级,目的是为了提高获得和释放的效率。二、对象头的介绍...
  • java并发机制的底层实现原理 重点的关键字、词汇 cpu中的一些术语 volatile volatile可以看做是是轻量级的synchronized 在多处理器开发中保证了共享变量的“可见性” (可见性的意思是当一个线程修改一个共享...
  • 锁机制Java锁机制其实就是一种等待机制,将多个线程对共享数据的并发访问转换为串行访问,即一个共享数据一次只能被一个线程访问,这样锁就可以用来保障线程安全了。锁(Lock)可以理解为对共享数据进行保护的一个许可...
  • 第一部分:synchronized和volatile锁机制用来保护对象的一致性以及操作的原子性,是实现线程安全的重要手段。线程安全涉及到对象两个重要的状态:共享性和可变性。如果对象是不可变的、线程私有的那么它一定是线程...
  • Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他单独获得这个变量。volatile借助Java内存模型保证所有线程能够看到最新的值。(内存可见性)实现原理:将带有...
  • 有需要的可以先去看看这篇操作系统教材上关于死锁的知识:操作系统 关于死锁的面试题 死锁的必要条件有四个,只要破坏其中一个,就可避免死锁。 互斥条件:在一段时间内,某资源只能被一个进程...那么在Java中,具体
  • 注意wait与notify是Java同步机制中的重要组成部分。结合与synchronized关键字使用,可以建立很多优秀的同步模型,例如生产者-消费者模型。但是在使用wait()、notify()、notifyAll()函数的时候,需要特别注意以下几...
  • 转自:http://blog.csdn.net/yangzhijun_cau/article/details/6432216一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在java里边就是拿到某个同步对象的(一个对象只有一把);...
  • 上一篇Blog介绍了什么是并发并发编程的挑战,本篇Blog我们来学习下Java的底层对并发是如何支持的,也就是Java底层的并发机制到底是什么样的?在JVM系列的Blog我们知道,Java代码在编译后会变成Java字节码,字节码...
  • Java--多线程锁机制

    2021-10-16 11:11:23
    上次通过三个例子,了解了Java并发三个特性,也分析了volatile不能解决原子性问题的原因,要解决原子性问题,就需要用到 一、轻量级与重量级 1.的概念 :一个线程对共享对象进行加锁,别的线程访问该对象...
  • 当然Java锁机制如synchronize和lock也是可以保证可见性的,加锁可以保证在同一时刻只有一个线程在执行同步代码块,释放锁之前会将变量刷回至主存,这样也就保证了可见性。 关于线程不安全的表现还有『有序性』,...
  • Java并发

    2021-04-18 01:51:41
    Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而main函数所在的线程就是这个进程中的一个线程,也称主线程。何为线程?线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的...
  • java并发机制并发机制下数据安全问题的解决方案 说在前面:线程和进程 在提及多线程的时候,必须引入线程这个概念,而线程往往是和进程联系在一起的。线程和进程的关系,举一个例子,一家公司是一个进程,网景...
  • 轻量级锁快速上锁机制fast locking • 在当前线程栈中创建存储锁记录的空间 • 将对象头mark work复制到lock record中(displaced mark word) • 线程尝试用CAS将mark word替换为指向锁记录lock recordœ的指针 • ...
  • 一、线程安全、 二、锁机制 ( 类锁 | 对象锁 )、 三、锁分类 ( 轻量级锁 | 重量级锁 )、
  • Java并发集合

    2021-02-27 16:49:06
    Java并发集合并发集合实现1JDK1.5的出现,对于集合并发编程来说,java developer有了更多的选择。不过,在JDK1.5之前,Java也还是提供了一些解决方案。(1)最为简单直接的就是在程序中我们自己对共享变量进行加锁。...
  • 当我们在使用Java进行网络编程时经常会遇到很多...而对于Java并发来说,与超时相关的内容主要是线程等待超时和获取超时,比如调用Object.wait(long)就会使线程进入等待状并在指定时间后等待超时。此篇主要讲解Java...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 174,317
精华内容 69,726
关键字:

java并发的锁机制

java 订阅