Java 锁中的基本概念
1. AQS – 指AbstractQueuedSynchronizer类。
AQS是java中管理“锁”的抽象类,锁的许多公共方法都是在这个类中实现。AQS是独占锁(例如,ReentrantLock)和共享锁(例如,Semaphore)的公共父类。
2. AQS锁的类别 – 分为“独占锁”和“共享锁”两种。
(01) 独占锁 – 锁在一个时间点只能被一个线程锁占有。根据锁的获取机制,它又划分为“公平锁”和“非公平锁”。公平锁,是按照通过CLH等待线程按照先来先得的规则,公平的获取锁;而非公平锁,则当线程要获取锁时,它会无视CLH等待队列而直接获取锁。独占锁的典型实例子是ReentrantLock,此外,ReentrantReadWriteLock.WriteLock也是独占锁。
(02) 共享锁 – 能被多个线程同时拥有,能被共享的锁。JUC包中的ReentrantReadWriteLock.ReadLock,CyclicBarrier, CountDownLatch和Semaphore都是共享锁。这些锁的用途和原理,在以后的章节再详细介绍。
3. CLH队列 – Craig, Landin, and Hagersten lock queue
CLH队列是AQS中“等待锁”的线程队列。 在多线程中,为了保护竞争资源不被多个线程同时操作而起来错误,我们常常需要通过锁来保护这些资源。在独占锁中,竞争资源在一个时间点只能被一个线程锁访问;而其它线程则需要等待。CLH就是管理这些“等待锁”的线程的队列。
CLH是一个非阻塞的 FIFO 队列。 也就是说往里面插入或移除一个节点的时候,在并发条件下不会阻塞,而是通过自旋锁和 CAS 保证节点插入和移除的原子性。
4. CAS函数 – Compare And Swap
CAS函数,是比较并交换函数,它是原子操作函数;即,通过CAS操作的数据都是以原子方式进行的。例如,compareAndSetHead(), compareAndSetTail(), compareAndSetNext()等函数。它们共同的特点是,这些函数所执行的动作是以原子的方式进行的。
5. Reentrantlock
6. synchronized和Reentrantlock
来聊一下他们两个的区别:
他们都是io阻塞锁,线程运行的时候,如果被另一个线程加锁,需要等另一个线程运行完,才能运行。
Synchronized会自己自动释放锁,Reentrantlock则需要自己手动释放锁,而且手动释放锁必须放在finally里面unlock,建议新手用前者。
Reentrantlock是可以公平,可以中断响应,限制等待时间。
7. 可重入锁与不可重入锁
重入锁实现可重入性原理或机制是:每一个锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁。
synchronized为可重入锁,如下示例
public class TestDemo10 {
public static synchronized void lock1(){
System.out.println("lock1");
lock2();
}
public static synchronized void lock2(){
System.out.println("lock2");
}
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
TestDemo10 lock = new TestDemo10();
lock.lock1();
}
}.start();
}
}
输出
lock1
lock2
Reentrantlock为可重入锁,如下示例
Lock lock = new ReentrantLock();
int i = 0;
try{
lock.lock();
i++;
System.out.println(i);
lock.lock();
i++;
System.out.println(i);
} finally {
lock.unlock();
lock.unlock();
}
输出
1
2
锁的可定时性
Thread thread1 = new Thread("thread1"){
@Override
public void run() {
try{
if(fixtimelock.tryLock(5, TimeUnit.SECONDS)){
Thread.sleep(6000);
System.out.println(Thread.currentThread().getName()+"获取锁成功");
}else{
System.out.println(Thread.currentThread().getName()+"获取锁失败");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
fixtimelock.unlock();
}
}
};
Thread thread2 = new Thread("thread2"){
@Override
public void run() {
try{
if(fixtimelock.tryLock(5, TimeUnit.SECONDS)){
Thread.sleep(6000);
System.out.println(Thread.currentThread().getName()+"获取锁成功");
}else{
System.out.println(Thread.currentThread().getName()+"获取锁失败");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if(fixtimelock.isHeldByCurrentThread()){
fixtimelock.unlock();
}
}
}
};
thread1.start();
thread2.start();
运行结果为:
thread2获取锁失败
thread1获取锁成功
因为线程1先获得锁,执行睡眠6秒的操作,睡眠中不会释放掉锁,因此线程2在5秒内没有获取到锁。