本文先尝试使用ReenTrantLock
,获取锁、释放锁以及等待唤醒机制,后期分析下原理。
lock
我们知道synchronized关键字可以实现线程同步,确实SE1.5之前关于线程同步大多使用synchronized实现,它可以隐式的加锁解锁(monitorenter和monitorexit),无需开发者关心底层。
SE1.5之后并发包下引入了lock接口,和synchronized一样拥有锁功能,虽需要显示获取、关闭锁,但它拥有了锁获取和释放的可操作性,可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。
例子:
ReentrantLock是lock的一个实现类:使用lock的一般操作为:
try{
lock.lock();
}catch(){
...业务
}finally{
lock.unlock();
}
Lock接口API
public class ReentrantLock implements Lock, java.io.Serializabl
void lock(); //获取锁
void lockInterruptibly() throws InterruptedException;//获取锁的过程能够响应中断
boolean tryLock();//非阻塞式响应中断能立即返回,获取锁反回true反之返回fasle
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;//超时获取锁,在超时内或者未中断的情况下能够获取锁
Condition newCondition();//获取与lock绑定的等待通知组件,当前线程必须获得了锁才能进行等待,进行等待时会先释放锁,当再次获取锁时才能从等待中返回
接下来我们尝试使用这些方法
lock()方法
和Sychronized一样会以阻塞的方式实现同步,只不过lock需要主动释放锁。
public class LockDemo01 {
private static int a = 0;
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
lock.lock();
for (int i1 = 0; i1 < 10000; i1++) {
a++;
}
}finally {
lock.unlock();
}
}).start();
}
Thread.sleep(2000);
System.out.println(a);
}
}
lockInterruptibly()
lockInterruptibly()方法获取锁的过程可响应中断。如果两个线程竞争获取锁(lock.lockInterruptibly()),此刻线程t1获取了锁,那么线程t2只有等待,如果此刻对线程t2调用t2.interrupt()方法时,会阻止线程t2的等待过程。
如果没有中断操作的话此方法获取的锁和lock方法没有区别。
尝试获取可响应中断锁的线程正在运行时(或者被阻塞在代码入口处)被中断时会抛出InterruptedException在catch代码块捕获并执行其他业务。
public class LockTest {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "线程尝试获取可响应中断锁");
try {
lock.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + "执行");
int i = 10;
while (i-- > 0) Thread.sleep(200);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "被中断");
} finally {
//lock必须手动释放
lock.unlock();
System.out.println(Thread.currentThread().getName() + "释放锁");
}
});
Thread t2 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "线程尝试获取可响应中断锁");
try {
lock.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + "执行");
} catch (InterruptedException e) {
//响应中断线程
System.out.println(Thread.currentThread().getName() + "被中断");
} finally {
//处理异常 如果线程没有获取锁而手动关闭的化 会抛出异常
try {
//lock必须手动释放
lock.unlock();
System.out.println(Thread.currentThread().getName() + "释放锁");
} catch (Exception e) {
System.out.println("被中断的线程:" + Thread.currentThread().getName() + "运行结束");
}
}
});
t1.start();
//睡一会 t1先执行
Thread.sleep(100);
t2.start();
//中断线程2 如果不中断 和lock是一样的
t2.interrupt();
}
}
tryLock()
lock.tryLock(200, TimeUnit.MILLISECONDS)
lock.tryLock()
- lock.tryLock()
非阻塞式响应中断能立即返回,获取锁反回true反之返回fasle。不会阻塞不会自旋。
- lock.tryLock(200, TimeUnit.MILLISECONDS)
在指定时间范围内获取锁会立刻返回true,指定时间范围内未获取锁不会立刻返回false,超过指定时间范围返回false。类似于超时范围。
此例线程t2循环等待(类似自旋),t1释放锁
class TryLockTest2 {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
//获取锁成功
if (lock.tryLock()) {
try {
System.out.println(Thread.currentThread().getName() + "线程获取锁成功,开始做事");
int i = 10;
while (i-- > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}finally {
System.out.println(Thread.currentThread().getName() + "线程释放锁");
lock.unlock();
}
}
//获取锁失败
else {
System.out.println(Thread.currentThread().getName() + "线程获取锁失败,做其他事");
}
});
Thread t2 = new Thread(() -> {
//一直等待成功获取锁
while (!lock.tryLock()) System.out.println("等待获取锁");
try {
//获取锁成功
System.out.println(Thread.currentThread().getName() + "线程获取锁成功,开始做事");
}finally {
System.out.println(Thread.currentThread().getName() + "线程释放锁");
lock.unlock();
}
});
t1.start();
Thread.sleep(500);
t2.start();
//保证非main线程运行成功
Thread.sleep(500);
}
}
newCondition();
newCondition()方法获取与lock绑定的等待通知组件,当前线程必须获得了锁才能进行等待,进行等待时会先释放锁,当再次获取锁时才能从等待中返回。
//当前线程进入等待状态,如果其他线程调用condition的signal或者signalAll方法并且当前线程获取Lock从await方法返回,如果在等待状态中被中断会抛出被中断异常;
void await() throws InterruptedException;
//当前线程进入等待状态直到被通知,中断或者超时;
long awaitNanos(long nanosTimeout) throws InterruptedException;
//同二 支持自定义时间
boolean await(long time, TimeUnit unit)throws InterruptedException:
//当前线程进入等待状态直到被通知,中断或者到了某个时间
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();//唤醒当前condition上的线程(应该是队列)
void signalAll();//唤醒当前condition上的所有
- 使用await()方法前必须先获取锁,await()方法会释放锁和cpu分配的时间片。
- 调用await()使得线程进入等待状态,如果被中断会抛出中断异常。
- await(long time, TimeUnit unit)等待指定时长,必须再次获得锁才可以从await返回,也就是如果调用此await方法后,锁被其他线程占有,则超过定义时间范围也不会唤醒。
- 使用signal()方法前必须先获取锁。唤醒同一个condition上的线程(等待队列,单向链表)
例子:
public class ConditionTest {
//lock
private static ReentrantLock lock = new ReentrantLock();
//condition
private static Condition condition = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "尝试线程获取锁");
//调用condition的await()方法前需要首先获取锁
lock.lock();
System.out.println(Thread.currentThread().getName() + "线程已经获取了锁");
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();
}
});
Thread t2 = new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "尝试线程获取锁");
//调用condition的await()方法前需要首先获取锁
lock.lock();
System.out.println(Thread.currentThread().getName() + "线程已经获取了锁");
System.out.println(Thread.currentThread().getName() + "线程开始执行业务");
System.out.println(Thread.currentThread().getName() + "唤醒该condition上的等待线程");
condition.signal();
} finally {
lock.unlock();
}
});
t1.start();
//t1先启动
//Thread.sleep(5);
t2.start();
//保证非main线程执行
Thread.sleep(1000);
}
}
await&signal VS wait¬ify
- awit&signal属于Condition类下,wait¬ify属于Object类下。
- await需要获取lock,wait需要获取monitor
- await属于java语言级别,wait属于java底层。(前者拓展性更好)
- await更灵活:可响应中断、可设置多个等待队列、自定义超时时间。而wait不行
- await和wait都会释放锁交出时间片。
Thread.sleep();方法不会释放锁。
wait测试
Object.wait()必须获取对象监视器(monitor),也就是必须在同步代码快里执行。调用wait()会释放锁交出时间片,一直等待直到其他线程唤醒(notify)它。
class ObjectWaitAndThreadSleep {
public static void main(String[] args) throws InterruptedException {
ObjectWaitAndThreadSleep objectWaitAndThreadSleep = new ObjectWaitAndThreadSleep();
objectWaitAndThreadSleep.ObjectWait();
}
public void ObjectWait() throws InterruptedException {
//监视器
Object o = new Object();
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "尝试线程获取锁");
synchronized (o) {
System.out.println(Thread.currentThread().getName() + "线程已经获取了锁");
System.out.println(Thread.currentThread().getName() + "线程开始执行业务");
try {
System.out.println(Thread.currentThread().getName() + "线程等待");
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "线程被唤醒,继续执行");
}
});
Thread t2 = new Thread(() -> {
synchronized (o) {
System.out.println(Thread.currentThread().getName() + "尝试线程获取锁");
System.out.println(Thread.currentThread().getName() + "线程已经获取了锁");
System.out.println(Thread.currentThread().getName() + "线程开始执行业务");
System.out.println(Thread.currentThread().getName() + "唤醒其他线程");
o.notify();
}
});
t1.start();
Thread.sleep(1000);
t2.start();
Thread.sleep(500);
}
}