1. 公平锁和非公平锁
ReentrantLock默认是非公平锁
对于Synchronized也是非公平锁
ReentrantLock lock = new ReentrantLock(false);
公平锁:多个线程按照申请锁的顺序来获取锁,类似排队,先来先服务。
非公平锁:多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发情况下,有可能会造成优先级反转或饥饿现象。优点在于吞吐量比公平锁大
2. 可重入锁(递归锁)
ReentrantLock和Synchronized也是可重入锁
同一线程外城函数获得锁之后,内层递归函数依旧能获取该锁的代码;同一个线程在外层方法获取锁时,在进入内层方法会自动获取锁。也就是说,线程可以进入任何一个他已经拥有的锁所同步着的代码块
作用是防止死锁
案例:证明synchronized和ReentrantLock是一个可重入锁
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Phone implements Runnable{
public synchronized void sendEmail(){
System.out.println(Thread.currentThread().getName() + "\t sendEmail...");
}
public synchronized void sendEms(){
System.out.println(Thread.currentThread().getName() + "\t sendEms...");
sendEmail();
}
Lock lock = new ReentrantLock();
@Override
public void run() {
get();
}
public void get(){
lock.lock();
try{
System.out.println(Thread.currentThread().getName() + "\t invoke get method...");
set();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void set(){
lock.lock();
try{
System.out.println(Thread.currentThread().getName() + "\t invoke set method...");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public class ReentrantLockDemo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread( () -> {
phone.sendEms();
}, "t1").start();
new Thread( () -> {
phone.sendEmail();
}, "t2").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("=============================");
new Thread( phone,"t3").start();
}
}
注意:对于lock和unlock方法需要两两配对,加锁几次,需要解锁几次
3. 独占锁(写锁)和共享锁(读锁)/互斥锁
独占锁:该锁一次只能被一个线程所持有。对ReentrantLock和Synchronized而言都是独占锁
共享锁:该锁可被多个线程所持有
对ReentrantReadWriteLock其读锁是共享锁,写锁是独占锁
读锁的共享锁可以保证并发性是非常高效的,读写,写写以及写读的过程是互斥的
4. 自旋锁
尝试获取锁的线程不会阻塞,而是采用循环的方式去尝试获取锁,好处是减少线程上下文切换的消耗,没有类似wait的阻塞;缺点是循环时间会消耗CPU,只能保证一个共享变量的原子操作,容易引起ABA问题
实例分析:通过CAS完成自旋锁,A线程先进来调用Lock方法自定已持有锁5s,B线程随后进来发现当前有线程持有锁,不是null。所以只能通过自旋等待,直到A释放锁B才能抢到
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class SpinLockDemo {
// 带有Thread泛型的原子引用
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void MyLock(){
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + "\t lock...");
// 第一次判断时,满足条件compareAndSet返回true取反之后返回false
// 则直接跳过 while判断 当第二个线程来调用时候,相反while为true,则会在while处一直判断当前是不是null(自旋)
while(!atomicReference.compareAndSet(null, thread)){
}
}
public void MyUnlock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread, null);
System.out.println(thread.getName() + "\t unlock...");
}
public static void main(String[] args) {
SpinLockDemo lock = new SpinLockDemo();
new Thread( () -> {
lock.MyLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.MyUnlock();
}, "AAA").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread( () -> {
lock.MyLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.MyUnlock();
}, "BBB").start();
}
}
5. Synchronized和Lock的区别
-
原始构成:
Synchronized是关键字,属于JVM层面;底层是monitorenter正常退出和monitorexit异常退出(通过monitor对象来完成),其实wait/notify方法也依赖于monitor对象,只有在同步方法或者同步代码块中才能调用wait/notify等方法
lock是一个具体类(JUC下的locks包下的类),是API层面的锁 -
使用方法:
synchronized不需要用户手动去释放锁,当synchronized代码执行后系统会自动让线程释放对锁的应用
ReentrantLock则需要用户手动去释放锁,若没有释放锁,则会出现死锁现象 -
等待是否可中断:
synchronized不可中断,除非抛出异常或者正常运行完毕
ReentrantLock可以中断:设置超时方法tryLock(timeout,unit);lockInterruptibly()代码中,调用interrupt()方法可中断 -
加锁是否公平:
synchronized是非公平锁
ReentrantLock默认是非公平锁,构造方法可以传入false或者true,true为公平锁,false为非公平锁 -
锁绑定多个条件Condition:
synchronized没有,synchronized只能唤醒一个或者全部唤醒线程
ReentrantLock用来实现分组唤醒需要唤醒的线程,做到精确唤醒