背景
synchronized关键字的缺陷
构造一个显式的BooleanLock,使其在具备synchronized关键字所有功能 的同时又具备可中断 和lock超时 的功能。 |
定义Lock接口
public interface Lock {
void lock() throws InterruptedException;
void lock(long mills) throws InterruptedException, TimeoutException;
void unlock();
List<Thread> getBlockedThreads();
}
在上述代码中: |
·lock()方法永远阻塞 ,除非获取到了锁,这一点和synchronized非常类似,但是该方法是可以被中断 的,中断时会抛出InterruptedException异常。 |
·lock(long mills)方法除了可以被中断 以外,还增加了对应的超时 功能。 |
·unlock()方法可用来进行锁的释放 。 |
·getBlockedThreads()用于获取当前有哪些线程被阻塞。 |
实现BooleanLock
public class BooleanLock implements Lock {
private Thread currentThread;
private boolean locked = false;
private final List<Thread> blockedList = new ArrayList<>();
@Override
public void lock() throws InterruptedException {
synchronized (this) {
//①
while (locked) {
//②
blockedList.add(currentThread());
this.wait();
}
blockedList.remove(currentThread());//③
this.locked = true;//④
this.currentThread = currentThread();//⑤
}
}
@Override
public void lock(long mills) throws InterruptedException, TimeoutException {
synchronized (this) {
if (mills <= 0) {
//①
this.lock();
} else {
long remainingMills = mills;
long endMills = currentTimeMillis() + remainingMills;
while (locked) {
if (remainingMills <= 0) {
//②
throw new TimeoutException("can not get the lock during " + mills);
}
if (!blockedList.contains(currentThread())) {
blockedList.add(currentThread());
}
this.wait(remainingMills); //③
remainingMills = endMills - currentTimeMillis(); //④
}
blockedList.remove(currentThread()); //⑤
this.locked = true;
this.currentThread = currentThread();
}
}
}
@Override
public void unlock() {
synchronized (this) {
if (currentThread == currentThread()) {
//①
this.locked = false; // ②
Optional.of(currentThread().getName() + " release the lock.").ifPresent(System.out::println);
this.notifyAll(); // ③
}
}
}
@Override
public List<Thread> getBlockedThreads() {
return Collections.unmodifiableList(blockedList);
}
}
BooleanLock是Lock的一个Boolean实现,通过控制一个Boolean变量的开关来决定是否允许当前的线程获取该锁,首先定义三个非常重要的成员变量 |
其中currentThread代表当前拥有锁的线程 |
locked是一个boolean开关,false代表当前该锁没有被任何线程获得或者已经释放 ,true代表该锁已经被某个线程获得 ,该线程就是currentThread; |
blockedList用来存储哪些线程在获取当前线程时进入了阻塞状态 。 |
在上述lock方法代码中: |
①Lock()方法使用同步代码块的方式进行方法同步。 |
②如果当前锁已经被某个线程获得,则该线程将加入阻塞队列,并且使当前线程wait释放对this monitor的所有权。 |
③如果当前锁没有被其他线程获得,则该线程将尝试从阻塞队列中删除自己(注意:如果当前线程从未进入过阻塞队列,删除方法不会有任何影响;如果当前线程是从wait set中被唤醒的,则需要从阻塞队列中将自己删除)。 |
④locked开关被指定为true。 |
⑤记录获取锁的线程。 |
相比较lock()方法,lock(long mills)方法稍微有些复杂,我们逐步解释。在上述代码中: |
①如果mills不合法,则默认 调用lock()方法,当然也可以抛出参数非法的异常,一般来说,抛出异常是一种比较好的做法。 |
②如果remainingMills小于等于0,则意味着当前线程被其他线程唤醒 或者在指定的wait时间到了 之后还没有获得锁,这种情况下会抛出超时的异常。 |
③等待remainingMills的毫秒数,该值最开始是由其他线程传入的,但在多次wait的过程中会重新计算。 |
④重新计算remainingMills时间。 |
⑤获得该锁,并且从block列表中删除当前线程,将locked的状态修改为true并且指定获得锁的线程就是当前线程。 |
unlock()方法需要做的仅仅是将locked状态修改为false,并且唤醒wait set中的其他线程,再次争抢锁资源。但是需要注意的一点是,哪个线程加的锁只能由该线程来解锁: |
①判断当前线程是否为获取锁的那个线程,只有加了锁的线程才有资格进行解锁。 |
②将锁的locked状态修改为false。 |
③通知其他在wait set中的线程,你们可以再次尝试抢锁了,这里使用notify和notifyAll都可以。 |
至此BooleanLock的基本功能已经完成,当然读者也可以对其进行扩充,比如增加tryLock的功能,也就是说能获得锁便获得,获得不了就退出,压根不会阻塞。 |
使用BooleanLock
(1)多个线程通过lock()方法争抢锁
public class BooleanLockTest {
//定义BooleanLock
private final Lock lock = new BooleanLock();
//使用try..finally语句块确保lock每次都能被正确释放
public void syncMethod() {
try {
//加锁
lock.lock();
int randomInt = current().nextInt(10);
System.out.println(currentThread() + " get the lock.");
TimeUnit.SECONDS.sleep(randomInt);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
}
public static void main(String[] args) {
BooleanLockTest blt = new BooleanLockTest();
//定义一个线程并且启动
IntStream.range(0, 10).mapToObj(i -> new Thread(blt::syncMethod)).forEach(Thread::start);
}
}
Thread[Thread-0,5,main] get the lock.
Thread-0 release the lock.
Thread[Thread-1,5,main] get the lock.
Thread-1 release the lock.
Thread[Thread-9,5,main] get the lock.
Thread-9 release the lock.
Thread[Thread-2,5,main] get the lock.
Thread-2 release the lock.
Thread[Thread-8,5,main] get the lock.
Thread-8 release the lock.
Thread[Thread-5,5,main] get the lock.
Thread-5 release the lock.
Thread[Thread-7,5,main] get the lock.
Thread-7 release the lock.
Thread[Thread-4,5,main] get the lock.
Thread-4 release the lock.
Thread[Thread-6,5,main] get the lock.
Thread-6 release the lock.
Thread[Thread-3,5,main] get the lock.
Thread-3 release the lock.
根据控制台输出可以看到,每次都会确保只有一个线程能够获得锁的执行权限,这一点已经与synchronized同步非常类似。 |
(2)可中断被阻塞的线程
BooleanLockTest blt = new BooleanLockTest();
new Thread(blt::syncMethod, "T1").start();
TimeUnit.MILLISECONDS.sleep(2);
Thread t2 = new Thread(blt::syncMethod, "T2");
t2.start();
TimeUnit.MILLISECONDS.sleep(10);
t2.interrupt();
运行上面的程序,在T2线程启动10毫秒以后,主动将其中断,T2线程会收到中断信号,也就是InterruptedException异常,这样也就弥补了Synchronized同步方式不可被中断的缺陷。上述程序的运行结果如下: |
Thread[T1,5,main] get the lock.
java.lang.InterruptedException
at java.base/java.lang.Object.wait(Native Method)
at java.base/java.lang.Object.wait(Object.java:338)
at thread.lock.customlock.BooleanLock.lock(BooleanLock.java:25)
at thread.lock.customlock.BooleanLockTest.syncMethod(BooleanLockTest.java:17)
at java.base/java.lang.Thread.run(Thread.java:833)
T1 release the lock.
BooleanLock还存在一个问题,如果某个线程被中断,那么它将有可能还存在于blockList中,该问题的修复也非常简单,可以对BooleanLock的lock方法进行进一步的增强加以修复 |
public void lock() throws InterruptedException {
synchronized (this) {
while (locked) {
//暂存当前线程
final Thread tempThread = currentThread();
try {
if (!blockedList.contains(tempThread)) {
blockedList.add(tempThread);
}
this.wait();
} catch (InterruptedException e) {
//如果当前线程在wait时被中断,则从blockedList中将其删除,避免内存泄漏
blockedList.remove(tempThread);
//继续抛出中断异常
throw e;
}
}
blockedList.remove(currentThread());
this.locked = true;
this.currentThread = currentThread();
}
}
(3)阻塞的线程可超时
最后再写一个具有超时功能lock的使用示例,同样定义两个线程T1和T2,确保T1先执行能够最先获得锁,T2稍后启动,在1000ms以内未获得锁则会抛出超时异常 |
public void syncMethodTimeoutable() {
try {
lock.lock(1000);
System.out.println(currentThread() + " get the lock.");
int randomInt = current().nextInt(10);
TimeUnit.SECONDS.sleep(randomInt);
} catch (InterruptedException | TimeoutException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
BooleanLockTest blt = new BooleanLockTest();
new Thread(blt::syncMethod, "T1").start();
TimeUnit.MILLISECONDS.sleep(2);
Thread t2 = new Thread(blt::syncMethodTimeoutable, "T2");
t2.start();
TimeUnit.MILLISECONDS.sleep(10);
Thread[T1,5,main] get the lock.
java.util.concurrent.TimeoutException: can not get the lock during 1000
at thread.lock.customlock.BooleanLock.lock(BooleanLock.java:66)
at thread.lock.customlock.BooleanLockTest.syncMethodTimeoutable(BooleanLockTest.java:34)
at java.base/java.lang.Thread.run(Thread.java:833)
T1 release the lock.
还没有评论,来说两句吧...