java多线程研究: 锁的概念

23 篇文章 0 订阅
14 篇文章 1 订阅
ReentrantLock和synchronized两种锁定机制的对比
synchronized:
synchronized (lockObject) {   
  // update object state  
}  

这是一个独享非公平可重入锁。
把代码块声明为 synchronized,有两个重要后果,通常是指该代码具有 原子性(atomicity)和 可见性(visibility)。原子性意味着一个线程一次只能执行由一个指定监控对象(lock)保护的代码,从而防止多个线程在更新共享状态时相互冲突。
可见性则更为微妙;它要对付内存缓存和编译器优化的各种反常行为。

缺点:
无法中断一个正在等候获得锁的线程。
无法通过投票得到锁,如果不想等下去,也就没法得到锁。
synchronized是一个重量级的锁。

ReentrantLock 类:

这是一个独享非公平可重入锁。
java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。
ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。
ReentrantLock 默认是非公平锁。

Lock lock = new ReentrantLock();  
lock.lock();  
try {   
  // update object state  
}  
finally {  
  lock.unlock();   
} 
什么时候选择用 ReentrantLock 代替 synchronized

既然如此,我们什么时候才应该使用 ReentrantLock 呢?答案非常简单 —— 在确实需要一些 synchronized 所没有的特性的时候,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者锁投票。 ReentrantLock 还具有可伸缩性的好处,应当在高度争用的情况下使用它,但是请记住,大多数 synchronized 块几乎从来没有出现过争用,所以可以把高度争用放在一边。我建议用 synchronized 开发,直到确实证明 synchronized 不合适,而不要仅仅是假设如果使用 ReentrantLock “性能会更好”。请记住,这些是供高级用户使用的高级工具。(而且,真正的高级用户喜欢选择能够找到的最简单工具,直到他们认为简单的工具不适用为止。)。一如既往,首先要把事情做好,然后再考虑是不是有必要做得更快。

锁的各种分类

在这里插入图片描述

悲观锁/乐观锁

悲观锁:我们假设在多线程使用同一资源时会互相抢占资源,这种态度引起的措施叫悲观锁。悲观锁一般用synchronized或者Lock来加锁。
乐观锁:在使用资源时认为其他资源不会抢占资源,就不用使用加锁的方式,这就是乐观锁,一般使用CAS算法处理。

公平锁/非公平锁

公平锁是指多个线程按照申请锁的顺序来获取锁
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象
对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁默认是非公平锁非公平锁的优点在于吞吐量比公平锁大
对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。

可重入锁

可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。

synchronized void setA() throws Exception{
    Thread.sleep(1000);
    setB();
}

synchronized void setB() throws Exception{
    Thread.sleep(1000);
}

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

独享锁/共享锁

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

对于Java ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。
读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。
独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。
对于Synchronized而言,当然是独享锁。

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

这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。
轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

CAS算法

全名:Compare And Swap(比较与交换)
无锁算法:基于硬件原语实现,在不使用锁(没有线程被阻塞)的清况下实现多线程之间的变量同步。
Jdk中实现:java.util.concurrent包中的原子类(Atomiclnteger)就是通过CAS来实现了乐观锁。
在这里插入图片描述
算法涉及到三个操作数:

需要读写的内存值V
进行比较的值A
要写入的新值B

CAS算法执行过程:两个线程同时操作一个变量他们都会在内存中生成一个副本,首先将原始数据先把参数V赋值给本地参数A,然后比较两个参数A和B,B为要修改后的参数,如果不同则需要修改,此时第一个进程去修改了V值,修改为1,当第二个线程修改时,先进行比较,发现V和A不相同,则成为脏值,程序无法更新值,只能自旋,等待或报错。

自旋锁:在进行完上面的行为后,线程二如果进入自旋,就相当于再把V值读给A,A和B进行比较后,再A和V进行比较,如果A和V相同了,就可以进行交换了。
自旋锁的概念可以描述为当一个线程在获取锁的时候,如果锁已经被其他线程获取,那么该线程就会循环等待,不断判断锁是否被成功获取,自旋直到锁被成功获取才推出循环。需要操作系统切换cpu状态,会耗费一定的时间。
在这里插入图片描述

AQS同步器—AbstractQueuedSynchronizer—抽象队列同步

在这里插入图片描述
此同步器是一个可重入锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值