先说结论
synchronized为非公平锁,耗时代码块在while(true)中,执行完毕以后,大概率下次还会抢占锁,导致其他线程一直拿不到锁,从而阻塞。
synchronized代码块中,如果有sleep,此时锁并不会被释放,就算本次while循环执行完毕,释放了,由于synchronized是非公平锁,下次大概率还会进入sleep代码块,这样会导致锁被长期占用,从而阻塞其他线程。
sleep不会释放锁,但会让出cpu,wait会释放锁
如上代码:
线程1,向容器中写入缓存
线程2,判断容器中有没有数据,没有数据,sleep 100ms
有数据处理数据
代码看上去没什么问题。
但是,由于sleep期间,锁并未释放,导致线程1被阻塞,即容器无法被写入。
线程2调度分两种情况
- 线程调度时,线程2拿到锁,sleep 100ms,此时让出了cpu,但是由于synchronized非公平锁,线程可能会再次调度到线程2,导致线程2while(true)再次拿到锁,接着拿着锁sleep 100ms,如此往复,线程2可能一直拿到锁然后sleep,可达几十次甚至上百次。在此期间,线程1会一直阻塞。
2.线程2拿到锁,然后sleep 100ms,此时线程2让出cpu,然后切到其他不相关线程如线程3,线程3执行了一段耗时的代码,然后又切回了线程2,然后在执行线程2,在此期间,线程1一直阻塞,因为线程2一直站着锁。
以上两种情况都是线程2切换期间,都为释放锁导致。
因此,sleep期间,一定不能拿着锁睡,否则会导致锁长时间被占用,引起其他线程阻塞。
如果线程需要等待,需要搭配wait,notify将线程暂停,并释放锁
例如:
线程2:
synchronized(queue){
while(queue.isEmpty()){
queue.wait();
}
线程1:
synchronized(queue){
queue.add();
queue.notifyAll();
}
如下代码演示同步中的耗时代码块问题
import lombok.extern.slf4j.Slf4j;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedDeque;
public class TestSync {
public static void main(String[] args) {
Queue<String> con = new ConcurrentLinkedDeque<>();
ThreadA threadA = new ThreadA(con);
ThreadB threadB = new ThreadB(con);
threadA.setName("threadA");
threadB.setName("threadB");
threadB.start();
threadA.start();
}
}
@Slf4j
class ThreadA extends Thread {
Queue<String> con;
ThreadA(Queue<String> p) {
con = p;
}
@Override
public void run() {
super.run();
int a = 0;
while (true) {
synchronized (con) {
log.info("线程A获得锁"+a++);
try {
//模拟耗时操作
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
@Slf4j
class ThreadB extends Thread {
Queue<String> con;
ThreadB(Queue<String> p) {
con = p;
}
@Override
public void run() {
super.run();
int b=0;
while (true) {
synchronized (con) {
log.info("线程B获得锁"+b++);
}
}
}
}
1.线程A,线程B启动以后竞争con锁
2.线程A获得锁以后,执行一段耗时的代码(sleep模拟),然后释放锁
3.线程B获得锁很快执行完成,并释放锁。
通过jprofiler查看阻塞情况。
image.png
image.png
网友评论