java中的锁机制

1. 锁机制

        java中的锁机制是用来处理面对多线程并发情况下数据的一致性的。在我们操作一个对象或者调用一个方法前加锁,这样当其他线程也对该对象和方法进行访问时就需要获得锁,如果该锁被其他线程持有,那么该线程则进入阻塞队列等待获得锁。这样就保证了在同一时间只有一个线程在对该对象进行操作。

        java中的锁,从不同的角度有好几种划分,如:(悲观锁,乐观锁)、(公平锁,非公平锁)、(共享锁,独占锁)、(轻量级锁重量级锁)等。


2. 自旋锁

        当线程持有锁时,另一个线程访问该资源会进入阻塞队列等待该线程释放锁。该操作中,线程进行了一次内核态与用户态之间的切换这种行为比较耗费时间。而自旋锁认为,上一个线程会很快的释放锁,我不需要进入阻塞队列,我只需要等一会(自旋)。

        自旋过程是需要占用cpu资源的,如果长时间自旋得不到锁,则会浪费cpu的资源。因此需要设置自旋的时间,释放资源。

        java利用CAS实现自旋锁。


3. 乐观锁、悲观锁

        乐观锁: 对线程的安全问题总是持有乐观态度,认为每次访问数据时都不会有人来修改数据,因此就不加锁。在操作数据后会检查版本号是否与之前一致,如果不一致,则证明在操作数据期间有另一个人也修改了数据,所以会重新读取,修改,更新数据。

        悲观锁: 对线程的安全问题总是持有悲观态度,认为每次访问时都会有人修改数据,于是每次操作前都会加锁。


3.1 CAS

        CAS指 Compare And Swap(比较并交换),java中的乐观锁大部分是通过CAS来实现的。

        CAS操作中需要三个参数:V(内存地址)、E(内存地址中因该存在的值)、N(修改后的新值)。

        当我们去更新一个值的时候,会进行比较,如果内存地址中本应该存储的值与内存地址中真实存储的值一样,则将值E修改为N。否则视为修改期间有其他人动过这个值,本次更新失败,重新读取旧值。

CAS的缺点:

  • 如果CAS失败,会一直进行尝试。也就是CAS的 自旋 ,如果一直失败不断循环,则会给CPU带来比较大的压力。

  • ABA问题

  • 只有操作一个共享变量时具有原子性,在多个共享变量时需要加锁。

ABA问题:

        假设有三个线程:1)将旧值A更新为B 。2)将旧址A更新为B 。3)将旧址B更新为A 。

        此时线程1执行完毕,值更新为B。线程 2 因为一些原因发生了阻塞,导致线程 3 先执行,又将 B 更新为 A 。然后线程 2 执行,将 A 更新为 B 。

        这样数据就发生了错误。但我们可以通过增加版本号来解决。每对次更新数据就更改版本号,在CAS的比较阶段比较版本号,如果版本号也一致再更新数据,否则提交失败。这样就让CAS变得更加严谨了。

CAS的自旋 :

        在 java.util.concurrent.atomic 包下有一堆原子类,当并发情况下,具有排他性。即当有线程执行这些原子类中的方法时,其他线程会自旋等待。


4. 可重入锁

        在同一个函数内,一个线程如果已经在外层函数获得过一次锁,那么在内层函数该线程依旧可以获得另一把锁。java的可重入锁有:synchronized 和 ReentrantLock。

5. 公平锁和非公平锁

        公平锁: 在分配锁的时候考虑等待获取锁的队列优先级。先排的先分配,后来的往后稍稍。

        非公平锁: 分配时不考虑队列,直接获取锁。

        因为有时候任务的重要性和他所需要先耗费的时间不一定等价。如果仅仅按照排队的优先级,有时候效率会比较低。java中synchronized为非公平锁,ReentrantLock默认的lock方法为非公平锁。


6. 共享锁和独占锁

独占锁: 一次只允许一个线程访问一个资源,也叫互斥锁。

共享锁: 允许多个线程并发访问共享资源。读写锁就是共享锁。


6.1 读写锁

        ReadWriteLock,读写锁内部分为读锁和写锁,读写锁允许同时有大量线程进行读操作,但同时只能有一个线程写操作。且读锁与写锁互斥,即有线程写操作时不能进行读操作。


7. synchronized

        synchronized 是一个独占锁、悲观锁、可重入锁。synchronized 关键字可以修饰实例方法、静态方法、代码块。当修饰实例方法(非静态方法)时,线程访问需要持有实例(对象)的锁;修饰静态方法时,锁住的是当前类的Class对象;访问代码块时,锁住的是括号内指定的对象。

7.1 死锁

        假设有两个共享资源A和B、两个线程1和2。线程1持有资源A,线程2持有资源B。但是现在线程1又想访问资源B,线程2想访问资源A。都在等待获取对方的锁,等待对方释放锁,就产生了死循环,死锁。

产生死锁的条件:

  • 同一时间只能有一个线程访问该资源。

  • 锁不能被其他线程抢夺,持有锁的线程只能主动释放锁。

  • 线程等待获取锁时不会释放自己持有的锁。

  • 互相持有资源的锁,且互相等待释放锁。(形成一个环)


8. ReentrantLock

        ReentrantLock 是实现了Lock接口的类。是一个可重入锁。需要手动开启、释放锁。lock方法默认非公平锁。

        ReentrantLock 通过响应中断、可轮询锁、定时锁避免了死锁问题。

ReentrantLock 和 synchronized 区别:

  • 两者都是可重入锁

  • 前者是显式锁,后者是隐式锁;且前者需要手动开启和释放锁

  • 前者有响应中断、可轮询锁、定时锁避免了死锁问题

  • 前者可以得到获取锁的状态

  • 前者可以设置非公平锁


9. Volatile

Volatile是比synchronized更轻量级的同步机制。

9.1 Volatile的原理

        在多个线程操作同一资源时,每个线程会先从主存中复制一个资源的副本到自己的内存空间中,再对该资源进行操作,操作结束后会立即将更新后的资源写入主存中。其他拥有此资源副本的线程会使各自内存中的该资源副本失效,并重新从主存中读取。以达到线程安全的目的。Volatile保证了可见性:即一个线程对数据做出了修改,其他线程也能知道。

Volatile和synchronized的区别:

  • Volatile只能对变量使用,synchronized可以对方法、对象和类使用

  • Volatile 仅保证了可见性,但不保证原子性。(如果运算本身就不具有原子性,如:i++)

  • Volatile 不会造成线程阻塞

  • Volatile 禁止指令重排

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值