synchronized 锁存在几个问题
我们在使用synchronized锁时候,由于synchronized(锁升级和锁竞争本文不讨论
)特定锁机制,导致我们不能针对锁进行控制
如果到获取到锁,但迟迟没释放锁,或者说执行时间过长,但是没有出现异常,就不会释放锁。
static final Object lock = new Object();
synchronized (lock){
while (true){
//代码
}
}
如上述代码,除非这段代码执行完,锁才会释放。但此时代码是死循环,就不会一直释放锁,除非当前运行的这个线程死亡。
如一些场景中,我们可能需要业务最大执行10s,在10s内不管你有没有执行结束,都不等待你了,直接释放锁。
类似于Future
的get()
方法,指定时间内没获取到异步结果,就超时,直接抛出异常,当然结果也不会获取到结果。
try {
String future = new FutureTask<>(()->{
return "";
}).get(5,TimeUnit.SECONDS);
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
解决办法
我们都知道JDK1.5之后提供了一个新的锁也就是Lock接口ReentrantLock
锁
- 使用JDK提供的
ReentrantLock
显示锁 - 自己定义显示锁
- 自己在
synchronized
代码块里判断,执行时间大于多少,抛异常会自动释放锁
这里我们不讲述Lock锁
我们这里自己定义一个显示锁,了解一下锁释放的机制
synchronized锁
- 隐式锁
- 自动释放锁
- 不能手动释放锁
- 遇到异常会自动释放
- wait()会释放,其他线程去抢占锁
基于以上释放锁的原理,我们来定义一个Lock锁
首先分析一下需要加锁
和解锁
,加锁时候指定超时时间
如果锁被占用了,其他的线程我们可以保存下来,也就是阻塞队列。
注意
: 谁加锁,谁解锁
对此我们的接口如下
public interface Lock {
//加锁
void lock() throws TimeoutException;
//释放锁
void unLock();
// 超时
void lock(long millio) throws TimeoutException;
//获取正在等待的线程
int getWaitThreadList();
}
实现方法
package thread.thread.synchronizedtest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeoutException;
public class TryLock implements Lock {
//等待线程
private List<Thread> threadList;
//锁是否在使用
private volatile boolean isUsed = false;
private Thread cunrrentThread;
public TryLock() {
this.threadList = new ArrayList<>();
}
@Override
public synchronized void lock() throws TimeoutException {
//System.out.println(Thread.currentThread().getName() + " " + " get lcok ");
//锁被使用 也就是被获取
while (isUsed) {
//加入阻塞队列
threadList.add(Thread.currentThread());
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//当前加锁的线程
cunrrentThread = Thread.currentThread();
//加锁
isUsed = true;
System.out.println(Thread.currentThread().getName() + " get lcok");
}
@Override
public synchronized void unLock() {
//释放锁 谁加锁 谁释放锁
if (Thread.currentThread() == cunrrentThread) {
threadList.remove(Thread.currentThread());
isUsed = false;
System.out.println(Thread.currentThread().getName() + " rem lcok");
this.notifyAll();
}
}
@Override
public synchronized void lock(long millio) throws TimeoutException {
if (millio <= 0) {
this.lock();
}
long timeout = System.currentTimeMillis() + millio;
this.lock();
while (isUsed && Thread.currentThread() == cunrrentThread) {
//超时
if (System.currentTimeMillis() - timeout >= 0) {
//释放锁
isUsed = false;
throw new TimeoutException(Thread.currentThread().getName() + " ---> time out ");
}
}
}
@Override
public int getWaitThreadList() {
//获取的时候不能被改变 防止获取等待线程数量时候,线程对其改变
return Collections.unmodifiableList(threadList).size();
}
}
最后我们来测试
首先是不超时的锁
TryLock lock = new thread.thread.synchronizedtest.TryLock();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
lock.lock();
//这里模拟执行多少时间
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (TimeoutException timeOutExpection) {
System.out.println(Thread.currentThread().getName() + " lcok time out ");
} finally {
lock.unLock();
}
}, "t->>" + i).start();
}
Thread.sleep(2);
结果如下,可以看到始终是一个get lock ,rem lock之后 下一个线程才会抢到锁
超时锁
//省略
lock.lock(5000);
//省略
运行可以看到如下结果
始终是超时之后其他线程才会抢锁
抛异常解决
判断时间,抛异常释放锁
Random random = new Random(System.currentTimeMillis());
final Object lock = new Object();
for (int i = 0; i < 6; i++) {
new Thread(() -> {
long end = System.currentTimeMillis() + 3; // 3s超时
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " get lock ");
try {
Thread.sleep(random.nextInt(7));
} catch (InterruptedException e) {
e.printStackTrace();
}
if (System.currentTimeMillis() - end >= 0) {
throw new RuntimeException(Thread.currentThread().getName() + " time out ");
}
System.out.println(Thread.currentThread().getName() + " done ");
}
}, "thread:" + i).start();
}
当然我们可以看到,只有睡眠0s 没有睡眠的这个线程正常运行完了,其他的都执行时间大于等于3s 都抛出异常,并且释放锁。
为什么2s也抛异常了,2s这个先睡眠,再判断 再抛异常,这些都需要时间。
最后
- 基于对象锁
synchronized 释放锁完全是基于内部代码机制监控异常和执行结束以及锁对象的wait()方法才会释放锁。 - 代码块
只能是异常或者执行结束才会释放锁
其他知识点
Java 多线程基础
深入理解aqs
ReentrantLock用法详解
深入理解信号量Semaphore
深入理解并发三大特性
并发编程之深入理解CAS
深入理解CountDownLatch
Java 线程池