可重入锁:
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。Java中 ReentrantLock 和 synchronized 都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
可重入锁的种类:
隐式锁(即synchronized关键字使用的锁)默认是可重入锁。
Synchronized的重入的实现机理。
每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。
当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。
在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么Java虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。
当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。
显式锁(即Lock)也有ReentrantLock这样的可重入锁。
LockSupport:
用于创建锁和其他同步类的基本线程阻塞原语。官网
LockSupport中的 park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程
三种让线程等待唤醒的方法:
- 使用object中的wait()方法让线程等待,使用object中的notify()方法唤醒线程
//这两种都需要上锁,并唤醒在等待后面才能唤醒线程
static final Object object = new Object();
private static void test1() {
new Thread(() -> {
try {
//睡眠3秒钟,让其他线程先执行
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object) {
System.out.println(Thread.currentThread().getName() + " 线程启动");
try {
System.out.println(Thread.currentThread().getName() + " 线程等待");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 线程被唤醒");
}
}, "test1--A").start();
new Thread(() -> {
synchronized (object) {
//线程执行唤醒,在等待前面执行,后面的等待的线程不hi被唤醒
object.notify();
System.out.println(Thread.currentThread().getName() + " 线程启动 通知");
}
}, "test1--B").start();
}
- 使用Juc包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
//这两种都需要上锁,并唤醒在等待后面才能唤醒线程
static final Object object = new Object();
static final Lock lock = new ReentrantLock();
static final Condition condition = lock.newCondition();
private static void test2() {
new Thread(() -> {
try {
//睡眠3秒钟,让其他线程先执行
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
//上锁
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 线程启动");
System.out.println(Thread.currentThread().getName() + " 线程等待");
//线程等待
condition.await();
System.out.println(Thread.currentThread().getName() + " 线程被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
}, "test2--A").start();
new Thread(() -> {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " 线程启动 通知");
condition.signal();
} finally {
lock.unlock();
}
}, "test2--B").start();
}
- LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
//使用java.util.concurrent.locks.LockSupport中的park和unpark 可不用加锁,可以先唤醒后等待 但是根本不得等待相当与直接赋予许可证
private static void test3() {
Thread threadA = new Thread(() -> {
try {
//睡眠3秒钟,让其他线程先执行
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 线程启动");
System.out.println(Thread.currentThread().getName() + " 线程等待");
//线程等待
//他要给予许可证才能被唤醒
//park() 方法相当于消费许可证,有许可证就通行,没有消费到就等待
java.util.concurrent.locks.LockSupport.park();
System.out.println(Thread.currentThread().getName() + " 线程被唤醒");
}, "test3--A");
threadA.start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 线程启动 通知");
//通过传入的线程,来赋予线程许可证
//unpark() 方法相当于给线程发证,一个线程最多只能拥有一个许可证
java.util.concurrent.locks.LockSupport.unpark(threadA);
}, "test3--B").start();
}