Java 常见的锁

Posted 小猪会拱菜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 常见的锁相关的知识,希望对你有一定的参考价值。

java中锁的概念

简单的理解一下

乐观锁(也是自旋锁)
为了不放弃cpu执行事件,循环的使用cas技术(在更改值时先再次获取值看值是否与刚才获取的相同,不相同说明被其他线程改变,则不进行操作,进行while循环,直到相同为止,再对值进行操作)对数据尝试进行更新,直到成功。

悲观锁
假定会发生并发冲突,同步所有对数据的相关操作,从读数据就开始上锁。

乐观锁
假定没有冲突,在修改数据时,如果发现数据和之前获取的不一样,则读最新数据,重试后修改

公平锁
争抢锁的顺序,如果按先来后到,则为公平

不公平锁
与公平锁相反

独享锁(写)
给资源加上写锁,线程可以修改资源,其他线程不能再加锁;(单写)

共享锁(读)
给资源加上读锁后只能读不能改,其他线程也只能加读锁,不能加写锁;(多读)

可重入锁
线程拿到一把锁之后,可以自由进入同一把锁所同步的其他代码,比如一个方法中的synchronized块中再次调用该方法,再次进入synchronized块,lock也是如此,同一个线程lock获取锁后,可以再次获取,并且lock提供有获取锁的次数的方法,同时释放时,也要释放相应的次数,多次调用unlock

不可重入锁
与可重入锁相反

开始详细简绍

1.乐观锁
顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。

乐观锁适用于多读的应用类型,乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。

CAS全称 Compare And Swap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。java.util.concurrent包中的原子类就是通过CAS来实现了乐观锁。

2.悲观锁

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。

典型的悲观锁就是Java的同步synchronized关键字。

 

3.公平锁
就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己。

公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。

 

代码:

public class LockDemo {
  public static void main (string[] args) throws In terruptedException {
    ReentrantLock lock=new ReentrantLock(true) ;
    for.(inti=0;i<10;i++){
    new Thread(new Runnable() {
@Override 
  public void run() {
    lock . lock() ;
      System. out . println("我是第”+ Thread.currentThread().getName()+”个线程") ;
      lock.unlock() ;
      }).start();
    }
  }
}

结果:

 

 

 

4.非公平锁

上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式。

非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。

代码:

public class LockDemo {
public static void main(String[] args) throws InterruptedException {
  Reen trantLock lock = new Reen trantLock(false) ;
  for ( int i=0;i<10;i++){
    new Thread(new Runnable() {
@Override
  public void run() {
    lock.lock();
      System.out.printIn("我是第” + Thread. currentThread() . getName() +"个线程);
      lock.unlock();
      }).start();
    }
  }
}

 

 

运行结果:

 

 

5.独享锁和共享锁

独享锁和共享锁在你去读C.U.T包下的ReeReentrantLock和ReentrantReadWriteLock你就会发现,它俩一个是独享一个是共享锁。

独享锁:也叫互斥锁,是指该锁一次只能被一个线程所持有。常见的有ReentrantLock、ReadWriteLock。

共享锁:是指该锁可被多个线程所持有。常见的有CountDownLatch。

 

6.可重入锁

广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。ReentrantLock和synchronized都是可重入锁。

 

 上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB可能不会被当前线程执行,可能造成死锁。

 

7.不可重入锁

不可重入锁,与可重入锁相反,不可递归调用,递归调用就发生死锁。看到一个经典的讲解,使用自旋锁来模拟一个不可重入锁。

以上是关于Java 常见的锁的主要内容,如果未能解决你的问题,请参考以下文章

Java线程并发中常见的锁--自旋锁 偏向锁

java线程总结--synchronized关键字,原理以及相关的锁

认识并发中常见的锁

Java中的锁和分布式架构中的锁

java中ReentrantReadWriteLock读写锁的使用

Java中的锁