ReentrantLock
ReentrantLock
的使用ReentrantLock
可以完全替代synchronized
,提供了一种更灵活的锁.
ReenTrantLock
必须手动释放锁,为防止发生异常,必须将同步代码用try
包裹起来,在finally
代码块中释放锁.
public class T {
ReentrantLock lock = new ReentrantLock();
// 使用ReentrantLock的写法
private void m1() {
// 尝试获得锁
lock.lock();
try {
System.out.println(Thread.currentThread().getName());
} finally {
//
lock.unlock();
}
}
// 使用synchronized的写法
private synchronized void m2() {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
T t = new T();
new Thread(t::m1, "t1").start();
new Thread(t::m2, "t2").start();
}
}
ReentrantLock
获取锁的方法tryLock()
使用tryLock()
方法可以尝试获得锁,返回一个boolean
值,指示是否获得锁.
可以给tryLock
方法传入阻塞时长,当超出阻塞时长时,线程退出阻塞状态转而执行其他操作.
public class T {
ReentrantLock lock = new ReentrantLock();
void m() {
boolean isLocked = false; // 记录是否得到锁
// 改变下面两个量的大小关系,观察输出
int synTime = 4; // 同步操作耗时
int waitTime = 2; // 获取锁的等待时间
try {
isLocked = lock.tryLock(waitTime, TimeUnit.SECONDS); // 线程在这里阻塞5秒,尝试获取锁
if (isLocked) {
// 若五秒内得到锁,则执行同步操作
for (int i = 1; i <= synTime; i++) {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "持有锁,执行同步操作");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 使用tryLock()方法,尝试解除标记时,一定要先判断当前线程是否持有锁
if (isLocked) {
lock.unlock();
}
}
// 执行非同步操作
while (true) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "没持有锁,执行非同步操作");
}
}
public static void main(String[] args) {
T t = new T();
new Thread(t::m, "线程1").start();
new Thread(t::m, "线程2").start();
}
}
若我们设置同步操作耗时4秒,获取锁的等待时间为2秒,则程序执行结果如下. 我们发现线程2
在阻塞时间内没能抢到锁,直接执行非阻塞方法:
线程1持有锁,执行同步操作
线程1持有锁,执行同步操作
线程2没持有锁,执行非同步操作
线程1持有锁,执行同步操作
线程2没持有锁,执行非同步操作
线程1持有锁,执行同步操作
线程2没持有锁,执行非同步操作
线程1没持有锁,执行非同步操作
线程2没持有锁,执行非同步操作
线程1没持有锁,执行非同步操作
...
若我们设置同步操作耗时4秒,获取锁的等待时间为5秒,则程序执行结果如下. 我们发现线程2
在阻塞时间内成功抢到锁,先执行完同步方法才执行非同步方法:
线程1持有锁,执行同步操作
线程1持有锁,执行同步操作
线程1持有锁,执行同步操作
线程1持有锁,执行同步操作
线程2持有锁,执行同步操作
线程1没持有锁,执行非同步操作
线程2持有锁,执行同步操作
线程1没持有锁,执行非同步操作
线程2持有锁,执行同步操作
线程1没持有锁,执行非同步操作
线程2持有锁,执行同步操作
线程1没持有锁,执行非同步操作
线程2没持有锁,执行非同步操作
线程1没持有锁,执行非同步操作
线程2没持有锁,执行非同步操作
....
若我们设置同步操作耗时4秒,获取锁的等待时间为5秒,则程序执行结果如下. 我们发现线程2
在阻塞时间内成功抢到锁,先执行完同步方法才执行非同步方法:
线程1持有锁,执行同步操作
线程1持有锁,执行同步操作
线程1持有锁,执行同步操作
线程1持有锁,执行同步操作
线程2持有锁,执行同步操作
线程1没持有锁,执行非同步操作
线程2持有锁,执行同步操作
线程1没持有锁,执行非同步操作
线程2持有锁,执行同步操作
线程1没持有锁,执行非同步操作
线程2持有锁,执行同步操作
线程1没持有锁,执行非同步操作
线程2没持有锁,执行非同步操作
线程1没持有锁,执行非同步操作
线程2没持有锁,执行非同步操作
....
lockInterruptibly
使用lockInterruptibly
以一种可被中断的方式获取锁.
获取不到锁时线程进入阻塞状态,但这种阻塞状态可以被中断.主线程调用被阻塞线程的interrupt()
方法可以中断该线程的阻塞状态,并抛出InterruptedException
异常.
interrupt()
方法只能中断线程的阻塞状态
.若某线程已经得到锁或根本没去尝试获得锁,则该线程当前没有处于阻塞状态
,因此不能被interrupt()
方法中断.
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
// 线程1一直占用着lock锁
new Thread(() -> {
lock.lock();
try {
System.out.println("线程1启动");
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE); // 线程一直占用锁
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "线程1").start();
// 线程2抢不到lock锁,若不被中断则一直被阻塞
Thread t2 = new Thread(() -> {
try {
lock.lockInterruptibly(); // 尝试获取锁,若获取不到锁则一直阻塞
System.out.println("线程2启动");
} catch (InterruptedException e) {
System.out.println("线程2阻塞过程中被中断");
} finally {
if (lock.isLocked()) {
try {
lock.unlock(); // 没有锁定进行unlock就会抛出IllegalMonitorStateException异常
} catch (Exception e) {
}
}
}
}, "线程2");
t2.start();
// 4秒后中断线程2
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.interrupt();//告诉t2别傻等了,抛出异常
}
程序输出如下:
线程1启动
线程2阻塞过程中被中断
并不是所有处于阻塞状态
的线程都可以被interrupt()
方法中断,要看该线程处于具体的哪种阻塞状态
.
阻塞状态
包括普通阻塞
,等待队列
,锁池队列
.
普通阻塞
: 调用sleep()
方法的线程处于普通阻塞
,调用其interrupt()
方法可以中断其阻塞状态并抛出InterruptedException
异常等待队列
: 调用锁的wait()
方法将持有当前锁的线程转入等待队列
,这种阻塞状态只能由锁对象的notify
方法唤醒,而不能被线程的interrupt()
方法中断.锁池队列
: 尝试获取锁但没能成功抢到锁的线程会进入锁池队列
synchronized
锁的线程的阻塞状态不能被中断.ReentrantLock
的lock()
方法争抢锁的线程的阻塞状态不能被中断.ReentrantLock
的tryLock()
和lockInterruptibly()
方法争抢锁的线程的阻塞状态不能被中断. ???在初始化ReentrantLock
时给其fair
参数传入true
,可以指定该锁为公平锁
.
CPU默认的进程调度是不公平的
,也就是说,CPU不能保证等待时间较长的线程先被执行.但公平锁
可以保证等待时间较长的线程先被执行.
public class T implements Runnable {
private static ReentrantLock lock = new ReentrantLock(true);// 指定锁为公平锁
@Override
public void run() {
for (int i = 0; i < 100; i++) {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "持有锁");
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
T t = new T();
new Thread(t, "线程1").start();
new Thread(t, "线程2").start();
}
}
程序输出如下,发现两个线程严格交替执行
线程1持有锁
线程2持有锁
线程1持有锁
线程2持有锁
线程1持有锁
线程2持有锁
...
之前的wait notify 生产者消费者:
使用while原因:
wait()
方法中出现异常,若使用if
语句,则就会直接进入catch
语句,打印异常并退出if
语句,执行后面对数组的操作notifyAll原因:
await()
和signal()
方法与synchronized
关键字类似,ReentrantLock
锁也支持等待/通知机制.与synchronized
不同的是,不是将线程阻塞在锁上,而是将其阻塞在条件Condition
对象上,要通过Condition
对象调用这些方法.
Condition
对象Condition
对象将Object
的监视器方法(wait()
,notify()
和notifyAll()
)分解成截然不同的条件对象,使等待/通知机制支持多路等待.
多个Condition
对象被绑定到一个ReentrantLock
对象上,一个锁上可以绑定多个Condition
对象,用来控制多个执行路线的等待通知,可以通过锁对象的newCondition()
方法得到一个绑定到当前对象上的Condition
对象.
要注意的是,
Condition
对象是绑定到锁对象上的(可以理解为一种细粒度更高的锁),而不是绑定在线程上的.因此分析其wait()
,notify()
和notifyAll()
还是要针对锁来进行分析,而不是直接分析Condition
对应哪个线程.
自己线程变量自己用
控制变量只有自己线程可见,ThreadLocal是空间换时间,synchronized是时间换空间。