一、公平锁与非公平锁
公平锁: 是指多个线程竞争同一资源时[等待同一个锁时],获取资源的顺序是按照申请锁的先后顺序的;公平锁保障了多线程下各线程获取锁的顺序,先到的线程优先获取锁;
基本特点: 线程执行会严格按照顺序执行,等待锁的线程不会饿死,但整体效率相对比较低;
非公平锁: 是指多个线程竞争同一资源时,获取资源的顺序是不确定的,一般是抢占式的;非公平锁相对公平锁是增加了获取资源的不确定性,但是整体效率得以提升;
基本特点: 整体效率高,线程等待时间片具有不确定性;
ReentrantLock构造函数中可以直接传入一个boolean值fair,对公平性进行设置。当fair为true时,表示此锁是公平的,当fair为false时,表示此锁是非公平的锁;
ReentrantLock fairLock = new ReentrantLock(true);//公平的
ReentrantLock unFairLock = new ReentrantLock();//非公平的
二、可重用锁
可重用锁也叫做递归锁,指的是同一线程外层函数获得锁之后,内层递归函数仍然有获取该锁的代码,但不受影响。
在JAVA中ReentrantLock 和synchronized 都是可重入锁;
ReenTrantLock原理:ReenTrantLock的实现是一种自旋锁,通过循环调用CAS操作来实现加锁。
2.1 ReenterLockDemo
package com.zhang;
import java.util.concurrent.TimeUnit;
public class ReenterLockDemo {
//synchronized和ReenterLock均为重用锁演示,其中synchronized是非公平锁,ReenterLock默认非公平锁
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendMS();
}catch(Exception e){
e.printStackTrace();
}
},"t1").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
try {
phone.sendMS();
}catch(Exception e){
e.printStackTrace();
}
},"t2").start();
}
public static class Phone{
public synchronized void sendMS(){
System.out.println("当前线程名:"+Thread.currentThread().getName()+"\t 发送短信!");
sendEmail();
}
public synchronized void sendEmail(){
System.out.println("当前线程名:"+Thread.currentThread().getName()+"\t 发送邮件!");
}
}
}
测试结果:
ReentrantLock.Condition线程通信
Condition可以实现多路通知功能,也就是在一个Lock对象里可以创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择的进行线程通知,更加精确地进行调度,在调度线程上更加灵活,不再是笼统地申请同一把锁的等待线程全部启动。
ReentrantLock加锁的线程是通过Condition对象的await()、signal()/signalAll()方法来挂起当前线程(挂起前先调用lock.unlock()释放掉锁),唤醒相应线程(在某condition条件下等待的线程)先执行来实现的。
测试结果
2.2 ReentrantLock与synchronized简单对比
1.可重入性
ReentrantLock和synchronized都具有可重入性,写代码synchronized更简单,ReentrantLock需要将lock()和unlock()进行一一对应否则有死锁的风险;
2.锁的实现方式
Synchronized作为Java关键字是依赖于JVM实现的,而ReenTrantLock是JDK实现的,ReentrantLock则提供较为多样的实现方式和更多的功能;
3.公平性
ReentrantLock提供了公平锁和非公平锁两种API,开发人员完全可以根据应用场景选择锁的公平性;
synchronized是作为Java关键字是依赖于JVM实现,Java团队应该是优先考虑性能问题,因此synchronized是非公平锁。
三、自旋锁
是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
自旋锁的缺点
获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务,使用这种锁会造成busy-waiting。
自旋锁的优点
自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快。
3.1 SpinLockDemo
package com.zhang;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class SpinLockDemo {
AtomicReference atomicReference = new AtomicReference();
//自旋锁
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(()->{
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.myUnLock();
},"t1").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
spinLockDemo.myLock();
spinLockDemo.myUnLock();
},"t2").start();
}
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println("当前线程名:"+Thread.currentThread().getName()+"\t 进来了,已添加锁!");
while (!atomicReference.compareAndSet(null,thread)){
//自旋操作
}
}
public void myUnLock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println("当前线程名:"+Thread.currentThread().getName()+"\t 出去了,已释放锁!");
}
}
测试结果:
四、读写锁
ReadWriteLock管理一组锁,一个是只读的锁,一个是写锁。读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。
package com.zhang;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class MyCache{//资源类
private volatile Map map= new HashMap();
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
public void put(String key,Object value){//写操作
try {
rwLock.writeLock().lock();
System.out.println("线程名:"+Thread.currentThread().getName() + "\t正在写入"+key);
TimeUnit.MILLISECONDS.sleep(300);
map.put(key,value);
System.out.println("线程名:"+Thread.currentThread().getName() + "\t写入完成");
}catch (Exception e){
e.printStackTrace();
}finally {
rwLock.writeLock().unlock();
}
}
public void get(String key){//读操作
try {
rwLock.readLock().lock();
System.out.println("线程名:"+Thread.currentThread().getName() + "\t正在读取"+key);
TimeUnit.MILLISECONDS.sleep(300);
Object result = map.get(key);
System.out.println("线程名:"+Thread.currentThread().getName() + "\t读取完成,结果"+result);
}catch (Exception e){
e.printStackTrace();
}finally {
rwLock.readLock().unlock();
}
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 0; i <3 ; i++) {
final int tempInt = i;
new Thread(()->{
myCache.put(String.valueOf(tempInt),String.valueOf(tempInt));
},String.valueOf(i)).start();
}
for (int i = 0; i <3 ; i++) {
final int tempInt = i;
new Thread(()->{
myCache.get(String.valueOf(tempInt));
},String.valueOf(i)).start();
}
}
}
测试结果:
参考文章
https://blog.youkuaiyun.com/black_bird_cn/article/details/81913671
https://www.jianshu.com/p/9d3660ad4358
https://blog.youkuaiyun.com/qq_19431333/article/details/70568478
https://www.cnblogs.com/xiaoxi/p/7651360.html
https://blog.youkuaiyun.com/sinat_32873711/article/details/106619981