线程学习之锁,ReentrantLock,Synchronize

 

一.ReentrantLock特点:

 1.1 是独占锁并且是可重入的:

独占互斥的,需要手动释放锁;

可重入的,但是要释放相同次数的锁;

1.2 默认是非公平锁,也可以实现公平锁:

 非公平锁:当前线程直接尝试获取锁,不管自己是不是身处队尾;

公平锁:按照队列顺序来,前面还有就等待;

创建的时候,加上参数true是公平锁,公平锁能够避免线程饥饿

1.3 可以获取锁时限等待:(用来辅助解决死锁):

在规定时间内获取不到锁,tryLock(),返回true false;

1.4 可以中断线程lockInterruptibly():

1.5 condition:

condition 的使用代替了 wait  和 notify, 并且是在lock()方法调用之后使用,cndition signal()唤醒其他线程;

实现队列 :

 private Lock lock = new ReentrantLock();
    private Condition  incon = lock.newCondition(); //进队列
    private Condition  outcon = lock.newCondition(); //出队列
    private  LinkedList<Integer> list = new LinkedList<Integer>();
    private  int  size=100;

    public void inLock(int i){
        try {
                lock.lock();
               while (list.size() == size)
                incon.await(); //等待
                list.add(i);
            System.out.println("入队 "+i);
            outcon.signal(); //唤醒

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void outLock(){
        try {
            lock.lock();
            while (list.size() == 0)
                outcon.await(); //等待
            int e= list.removeFirst();
            System.out.println("出队列 "+e);
            outcon.signal(); //唤醒

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}

1.6 公平锁 非公平锁的介绍:

流程 是尝试获取锁,获取失败则封装成节点放入对象中;

而获取锁的具体过程是

公平锁;

先看下state =0,如果=,则说明是当前锁没被持有,并且判断是否有其他线程比当前线程在同步队列中等待的时间更长,后通过CAS操作获取同步状态,就获取锁;

否则检测下当前线程是否是等于锁线程,因为是可重入锁,所以会state++;等释放的时候在减去,一直减到0;

否则会添加到队列中;(在添加到双向队列中)

非公平锁:

先看下state =0,如果=,当前线程会直接先尝试修改state的状态,如果修改成功直接获得锁,如果修改不成功就还是按照公平锁来;

compareAndSetState 方法抢占式加锁,加锁成功则将自己设为持锁线程,并返回;
若加锁失败,则调用 acquire 方法,将线程置于同步队列尾部进行等待;
线程在同步队列中成功获取锁,则将自己设为持锁线程后返回;
若同步状态不为0,且当前线程为持锁线程,则执行重入逻辑;

1.7 AQS:reentrantLock基于aQs原理

AQS是基于链表的双向队列,头结点是当前获取锁的线程,对于所有没有获取锁的都排在队列中标,当头结点运行完成后,会从队列中删除;

 

2.Synchronize

synchronize 底层   任何一个对象都有一个monitor相关联,当montior被线程持有之后,对象就处于锁定状态,(存储在java对象头里的是);

原理:  jvm 通过进入和退出对象监视器实现(montior)来实现方法 、同步块的同步。

具体是在调用同步块之前插入一个montiorenter指令,退出的时候插入一个montiorexit指令。保证同一时刻只能有一个线程获取,其他线程被阻塞等待。

 

 

同步代码块:

当线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获取对象的锁;monitorenter指令插入到同步代码块的开始位置,monitorexit指令插入到同步代码块的结束位置。

同步方法:

当执行方法时,会检测Acc_SYNCHORNIZE 是否被设置过,如果设置了,就先进行获取montior,执行完之后释放montior,在执行期间其他线程不能够在获取。

对象在内存中的布局分为三块区域:对象头、对象实际数据和填充数据;

对象头:

对象头里面放的是:  类型指针(用来确定实例是属于哪个类) 和 标记字段;

标记字段放的是该实例运行时的一些信息,如哈希码,gc年龄代,  monitor引用指针等;

 monitor:是线程私有的数据结构,线程有一个monitor record表,每一个被锁住的对象都与一个montior相关联,

同时,montior中有一个ower字段,存放的是与持有锁的线程的唯一标志。

锁优化:

偏向锁: 

在大多数的情况,往往是不存在竞争的,并且很可能是同一线程连续多次获取这个锁,为了降低获得锁的代价,出现了偏向锁。

当该锁第一次被线程访问的时候, 线程访问该锁并获取锁,在对象头和栈帧中的锁记录里存储该线程的id。当该现场再次访问的时候,只需要简单的测试西夏,看对象头markword中存在的是否是该线程id,如果是则说明该线程已经获取到了该锁,不必在去通过cas比较来获得锁。 如果测试失败,则先去对象头中查看是否存在偏向锁的标志符=1,如果存在说明是偏向锁,则进行cas竞争尝试让markword中指向当前线程的id,如果不是则进行cas竞争获取锁。偏向锁转向轻量级锁。

核心原理:      如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。

适用于:          没有锁竞争的场合,偏向锁有很好的优化效果,毕竟极有可能连续多次是同一个线程申请相同的锁。但是对于锁竞争比较激烈的场合,偏向锁就失效了。

 

 

轻量级锁:

使用的是: 对绝大部分的锁,在整个同步周期内都不存在竞争”有访问锁,适用于不存在竞争的情况

当关闭偏向锁 或者 当多个竞争存在,导致偏向锁升级为轻量级锁。

具体

1.获取锁 。

第一步:判断是否处于无锁状态。如果是,则在当前线程的栈帧中创建一个lock record锁记录,将对象头中的markword存储到线程栈帧的锁记录中。 如果不是转向第三步。

第二步: 通过cas尝试将对象头中的markword指向线程栈帧中的锁记录表,如果成功表示当前线程获取锁,将锁标志位变成00。如果失败,尝试自旋锁获取,失败,转向第三步。

第三步:判断当前rmarkword指向的是否是当前线程的锁记录表,如果是,则表示当前线程已经获取锁。如果不是,存在竞争则转向重量级锁。

2. 释放锁

  取出在lock record中的保存的markword数据,尝试cas替换当前对象的markword,成功表示释放锁了。失败,则表示存在锁竞争,膨胀成重量级锁。

 

 

重量级锁:

理念: 对绝大部分的锁,在整个同步周期内都不存在竞争”

适用场景:   线程交替执行同步块的场合,如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。

 

锁粗化:

将多次连续的加锁/解锁操作,换成一个更大范围的锁操作。

锁消除:

对于些不必要的锁操作,撤销,节省无意义的锁操作消耗时间。

自旋锁:

当获取锁失败的时候,不会立刻挂起,而是持续一段时间进行尝试获取。看当前锁是不是可以在短时间内就释放了。

自适应自旋锁:

是为了解决自旋锁中自旋的次数问题,让jvm更加的智能。自旋的次数不固定,线程自旋成功了,下次这个锁自旋的次数增多;自旋失败了,成功的次数很少,那么这个锁对应的下次自旋的次数减少。

乐观锁悲观锁:

乐观锁:   乐观的认为在拿数据的时候,当前的数据没有其他线程进行修改,不加锁,当前线程在更新数据的时候,需要判断下当前数据是否被修改。

方式:

通过版本控制号 或者cas;

版本控制号:给数据设置个version版本控制号, 初始变量=0,线程在准备修改数据的时候,同时也会读取这个数字, 提交更新时, 会对比下现在数据的version和开始时读取的version, 二者一致才会进行提交version+1,否则重试,知道更新成功。

 CAS:

在准备修改数据的时候,会通过cas进行比较,cas有三个值,一个是预期的现存进行比较的值A,一个是将要写入的值B,一个是需要读写的内存上的值V,如果内存地址上的值与预期的值相等,则修改值。

使用场景 :读操作比较多,而很少发生写操作竞争的时候。

缺点: cas 的ABA问题,如果其他线程占用时间过长,当前新线程自旋一直等待,占用cpu,循环时间长开销大。只能保证一个变量的原子性操作;a=a+b;无法保证

优点:使用与冲突比较少的情况,多读操作,这样的话可以减少锁的开销,提高吞吐量。

悲观锁: 悲观的认为每次取数据的时候,会有其他线程修改数据,所以先加上锁,保证自己在修改读取数据的时候,其他线程无法进行操作。同一时刻只能有一个线程进行操作。

向retrantLock synchroncized 都是悲观锁的实现。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值