java:锁机制

多个线程竞争一片资源时,需要考虑先来后到,否则会发生线程并发问题,那么如何考虑先来后到,就会使用到锁机制

锁的类型:

乐观锁:读多写少,遇到并发写的可能性低,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁。但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较更上一次的版本号,如果一样则更新),如果失败则要重复复读 比较 写的操作。

基本都是通过CAS操作实现的,CAS是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。

悲观锁:悲观的思想,认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会阻塞直到拿到锁。

java中的悲观锁是synchronized

java线程阻塞的代价:

消耗大量的资源,因为线程阻塞或唤醒一个线程都需要操作系统的介入,需要在用户态和内核态之间切换,这种切换消耗大量的系统资源,因为用户态与内核态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递许多变量。参数给内核,内核也需要保护好用户态在切换时的一些寄存变量,变量等,为了便于内核态调用结束后切换回用户态继续工作。

问题:

1.如果线程状态切换是一个高频操作时,这将会消耗很多CPU处理时间。

2.如果对于那些需要同步的简单的代码块,获取锁挂起操作消耗的时间比用户代码执行的时间还要长,这种同步策略显然非常糟糕。

由于synchronized会导致争用不到锁的线程进入阻塞状态,所以说他是Java中的重量级锁。

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

 

线程悬挂需要消耗cpu的,如果一直获取不到锁,那么线程就一直占用cpu做无用功,所以需要设定一个自旋等待的最大时间。

如果持有锁的线程执行的时间超过自旋等待的最大时间仍没有释放锁,就会导致其它争用锁的线程在最大等待时间内还是获取不到锁,这时争用线程会停止自旋进入阻塞状态。

自旋锁的优缺点:

1.锁的竞争不激烈情况下,占用锁的时间非常短的代码块来说性能能大幅度的提升。因为自旋的消耗会小于线程阻塞挂起再唤醒的操作对的消耗,这些操作会导致线程发生两次上下文切换。

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

自旋锁时间阈值:

自旋锁的目的是为了占着cpu的资源不释放,等到获取到锁立即进行处理。但是如何去选择自旋的执行时间呢?如果自旋执行时间太长,会有大量的线程处于自旋状态占用cpu资源,进而会影响整体系统的性能。因此自旋的周期选择很重要。

1.6之后,适应性自旋锁意味着自旋的时间不在固定的,而是由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定,基本认为一个线程上下文切换的时间是最佳的一个时间。

Synchronized的作用:

  1.作用于方法时,锁住的是对象的实例(this)

2.当作用于静态方法时,锁住的是class实例,又因为class的相关数据存储在永久带,永久带是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁所有调用该方法的线程。

3.Synchronized作用于一个对象实例时,锁住的是所有该对象为锁的代码块。

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

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

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

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

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

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

!Owner:当前释放锁的线程。
OnDeck线程获取到锁资源后会变为owner线程,而没有得到锁资源的仍然停留在EntryList中。如果Owner线程被wait方法阻塞,则转移到waitset队列中,直到某个时刻通过notify或notifyall唤醒,会重新进入entrylist中。

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

偏向锁:偏向锁会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问。不存在多线程争用的情况,则线程是不需要触发同步的,这种情况下,就会给线程加一个偏向锁。

如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,jvm会消除它身上的偏向锁,将锁恢复到标准的轻量级锁。

偏向锁获取过程:
访问Mark Word中偏向锁的标识是否设置成1,锁标志位是否为01,确认为可偏向状态。

如果为可偏向状态,则测试线程ID是否指向当前线程,如果是,进入步骤5,否则进入步骤3。

如果线程ID并未指向当前线程,则通过CAS操作竞争锁。如果竞争成功,则将Mark Word中线程ID设置为当前线程ID,然后执行5;如果竞争失败,执行4。

如果CAS获取偏向锁失败,则表示有竞争。当到达全局安全点(safepoint)时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码。(撤销偏向锁的时候会导致stop the word)

执行同步代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值